CakePHPのsearch pluginのサンプルコードと記事をちょっと修正。Containableは必要でした

CakeDCのsearch pluginの記事が少ないので1個置いときますね。CakePHP Advent Calendar 2010 8日目 - kanonjiの日記のコードをちょっとだけ直しました。タグ検索などを実装するHABTMの検索で、プラグインのReadme.mdにはあったContainableビヘイビアを使わなかったんですが、やっぱり必要でした。無くても動くけど、余計なJOINが発生します。

修正点

$ git diff --no-prefix HEAD~ HEAD models/entry.php 
diff --git models/entry.php models/entry.php
index 70c7aef..1b54b43 100644
--- models/entry.php
+++ models/entry.php
@@ -81,19 +81,23 @@ class Entry extends AppModel {
     }
     
     public function searchByGroupps($data = array()){
+        $this->User->GrouppsUser->Behaviors->attach('Containable', array('autoFields' => false));
         $this->User->GrouppsUser->Behaviors->attach('Search.Searchable');
         $query = $this->User->GrouppsUser->getQuery('all', array(
             'conditions' => array('groupp_id'  => explode('|', $data['groupp_id'])),
             'fields' => array('user_id'),
+            'contain' => $this->User->Groupp->alias,
         ));
         return $query;
     }
     
     public function searchByTags($data = array()){
+        $this->EntriesTag->Behaviors->attach('Containable', array('autoFields' => false));
         $this->EntriesTag->Behaviors->attach('Search.Searchable');
         $query = $this->EntriesTag->getQuery('all', array(
             'conditions' => array('tag_id'  => explode('|', $data['tag_id'])),
             'fields' => array('entry_id'),
+            'contain' => $this->Tag->alias,
         ));
         return $query;
     }
https://github.com/kanonji/CakePHP-Search-plugin-sample/commit/38ee2a20dd374bd7bb72c8a3e814fed8c0451590

HABTMの検索にはIN演算子を使ったサブクエリーが使われます。[https://github.com/CakeDC/search/blob/master/models/behaviors/searchable.php#L106:title=getQuery()]でサブクエリーのSQLが生成されるので、検索全体でのContainableとは別に、Containableのアタッチとcontainの設定が必要みたいです。

array('autoFields' => false)について

なお、array('autoFields' => false)はauto-add needed fields to fetch requested bindingsというものらしく、よく分かりませんがtrueだと何か必要とされるフィールドを勝手に追加するようになるらしい。デフォルトがtrueなので明示的にfalseにしているようです。実際にtrueのままだと、'fields' => array('user_id')と指定しているのにGroupp.idが追加されました。IN演算子に渡すので2つ以上のフィールドを取得するとSQLエラー*1となってしまいます。

SQLでの違い


--- before.sql	2011-02-15 13:15:14.000000000 +0900
+++ after.sql	2011-02-15 13:15:26.000000000 +0900
@@ -27,9 +27,6 @@
         FROM
             groupps_users AS GrouppsUser
             LEFT JOIN
-            users AS USER ON
-            (`GrouppsUser`.`user_id` = `User`.`id`)
-            LEFT JOIN
             groupps AS Groupp ON
             (`GrouppsUser`.`groupp_id` = `Groupp`.`id`)
         WHERE

SQLで言うとこんな違いです。JOINする必要のないusersテーブルをJOINしちゃってました。これは、サンプルアプリで言うところのグループでの検索ですが、タグ検索のほうを動かせばentriesテーブルを余計にJOINしちゃっていました。

ところで

プラグインのReadme.mdだと、このサブクエリーを生成するメソッドってfindByTags($data = array())とかになってるんですよね。http://book.cakephp.org/ja/view/1026/findByと被ってる上に、メソッド内で$data['tags']と決め打ちだからコントローラーから呼べる作りじゃないし、微妙だなぁと思ってsearchByTags()にしてみました。でも、サブクエリーのSQLが貰えるだけのメソッドだから、もっと別の名前の方が良かったなと思ったり。

環境

Mac Mac OS X 10.5.8(Leopard)
MAMP 1.7.2
CakePHP 1.3.6
php 5.2.6
CakeDC Search plugin updating readme · CakeDC/search@668eb68 · GitHub

*1:#1241 - Operand should contain 1 column(s)