Zend_Db_Table の join でハマる

Zend_Db_Table で join を使って、テーブルを結合してデータを取ろうとしてハマった。

発行したい SQL はこんな感じ。

SELECT
     statuses.id,
     statuses.user_id,
     statuses.reply_user_id,
     statuses.comment,
     statuses.created_at,
     users.user_name,
     users.private_flag
FROM
     statuses
JOIN users ON users.id = statuses.user_id
WHERE
    statuses.user_id = ?
AND
    users.private_flag = ?

users テーブルと、statues テーブルを id と user_id で join する至って普通なもの。


O/R マッパとして Zend_Db_Table_Abstract を使用している。

<?php
Class Statuses extends extends Zend_Db_Table_Abstract
{
    protected $_name = 'statuses';
    protected $_primary = 'id';
}

で、呼び出しもとで、ドキュメント(http://framework.zend.com/manual/ja/zend.db.select.html#zend.db.select.building.join)に記述されているを参考に作った。

<?php
// $this->_dao は Statues クラスのオブジェクト
$select = $this->_dao->select()
                            ->from(array('s' => 'statuses'))
                            ->join(array('u' => 'users'), 'u.id = s.user_id', array('u.user_name', 'u.private_flag'))
                            ->where('s.user_id = ?', $id);
                            ->where('u.private_flag = ?', 0);
$rows = $this->_userDao->fetchAll($select)

上記を実行してみると、エラーになった。

SQLSTATE[42000]: Syntax error or access violation: 1065 Query was empty


Zend_Db_Select には __toString() というメソッドがあり、それを使用すると、SQL 文に変換してくれる。

<?php
var_dump($select->__toString());

これを実行してみると、以下のようなワーニングが出た。

Warning: Select query cannot join with another table in 


ドキュメントで、連結するテーブルのカラム名を指定しない場合の記述方法があったので、そちらを試してみたら、
ちゃんと正しく実行が出来る(ただし当然連結するテーブルのカラムの値は取れない)。


この現象がどうにも謎で検索してみたが、Zend_Db_Table で join を使っている良いサンプルや事例が見つける事が出来なかった。
で、zf-users.jp の IRC で聞いてみたら、id:wozozo さんが、
http://www.zfforums.com/zend-framework-components-13/databases-20/problems-join-672.html この症状と同じじゃない?
って見つけてくれた。
読んでみると、まさしく同一の現象。


で、レスの中に、

hi, i was told you need to set setIntegrityCheck to false

http://www.zfforums.com/zend-framework-components-13/databases-20/problems-join-672.html

とあったので、試してみた。

<?php
// $this->_dao は Statues クラスのオブジェクト
$select = $this->_dao->select()->setIntegrityCheck(false)
                            ->from(array('s' => 'statuses'))
                            ->join(array('u' => 'users'), 'u.id = s.user_id', array('u.user_name', 'u.private_flag'))
                            ->where('s.user_id = ?', $id);
                            ->where('u.private_flag = ?', 0);
$rows = $this->_userDao->fetchAll($select)

エラーにならずに、無事にデータを取る事が出来た!


この setIntegrityCheck(false) は何者かというと、ドキュメントに以下のように書いてあった。

Zend_Db_Table_Select の主な使用目的は、 制約を強要して正しい形式の SELECT クエリを作成することです。 しかし時には、Zend_Db_Table_Row の柔軟性が必要であって 行を更新したり削除したりすることはないということもあります。 そんな場合には、setIntegrityCheck に false を渡して行/行セットを取得することができます。 この場合に返される行/行セットは 'ロックされた' 行 (save()、delete() やフィールドの設定用メソッドを実行すると例外が発生する) となります。

http://framework.zend.com/manual/ja/zend.db.table.html#zend.db.table.fetch-all

制約を強制しているから、エラーとなっているよう。上記のドキュメントに書かれている通り、行を更新、削除をせず、
データを取得したいだけなので、 setIntegrityCheck(false) を指定しても問題なさそう。
# というか指定しないと取得できない。


自分一人では解決が難しいときに聞ける場所があるのは凄くありがたい。
ということで、何か分からない事があったり、情報を提供して貰えるのなら IRC の #zftalk-ja@freenode に参加すると、みんな幸せになれると思う。