暇人じゃない

CakePHP の Search Plugin で HABTM なモデルを検索する

以前書いた「CakePHP の Search Plugin で複数のモデルを使用する」という記事にたどり着いている方が結構いるみたいなので、Search Plugin で HABTM なモデルを検索する方法について書いてみます。

例としては、記事(posts)にタグ(tags)がついていて、検索ページで指定したタグがついている記事(posts_tags)を検索する、という具合ですね。

環境:

テーブル構成

posts テーブル:

posts_tags テーブル(記事とタグが関連付けされている中間テーブル):

tags テーブル:

アソシエーション

HABTM の with については無用なトラブルを避ける為にも指定した方が良いという事なので、指定しておきます。

Post:

// models/post.php
var $hasAndBelongsToMany = array(
    'Tag' => array(
        'className' => 'Tag',
        'joinTable' => 'posts_tags',
        'foreignKey' => 'post_id',
        'associationForeignKey' => 'tag_id',
        'unique' => true,
        'conditions' => '',
        'fields' => '',
        'with' => 'PostsTag',
        'order' => '',
        'limit' => '',
        'offset' => '',
    ),
);

PostsTag:

// models/posts_tag.php
var $belongsTo = array(
    'Tag' => array(
        'className' => 'Tag',
        'foreignKey' => 'tag_id',
        'conditions' => '',
        'fields' => '',
        'order' => ''
    ),
    'Post' => array(
        'className' => 'Post',
        'foreignKey' => 'post_id',
        'conditions' => '',
        'fields' => '',
        'order' => ''
    )
);

Model

// models/post.php
class Post extends AppModel {
    var $actsAs = array(
        'Search.Searchable',
        'Containable'
    );

    var $filterArgs = array(
        array('name' => 'tag_id', 'type' => 'subquery', 'method' => 'searchTag', 'field' => 'Post.id'),
    );

    function searchTag($data = array()) {
        $this->PostsTag->Behaviors->attach('Containable', array('autoFields' => false));
        $this->PostsTag->Behaviors->attach('Search.Searchable');

        $cond = explode('|', $data['tag_id']);
        $query = $this->PostsTag->getQuery('all', array(
            'conditions' => array('PostsTag.tag_id' => $cond),
            'group' => 'PostsTag.tag_id HAVING COUNT(PostsTag.tag_id) = '.count($cond),
            'fields' => array('post_id'),
            'contain' => array('Tag')
            )
        );
        return $query;
    }
}

$data['tag_id']1|2|3 と | で区切られてタグの ID が入ってくるので、explodearray(1, 2, 3) と配列にしています。

group 中のタグの絞り込みについてですが、これが最適なのかは分かりません。
もっと良いやり方があるよ!というツッコミ大歓迎です。

Controller

// controllers/posts.php
class PostsController extends AppController {
    var $components = array('Search.Prg');

    var $presetVars = array(
        array('field' => 'tag_id', 'type' => 'checkbox'),
    );

    function search() {
        $this->Prg->commonProcess();
        $this->paginate['conditions'] = $this->Post->parseCriteria($this->passedArgs);
        $this->set('posts', $this->paginate());
        $this->set('tags', $this->Post->Tag->find('list'));
    }
}

コントローラーは特に変わりません。 タグの一覧をセットするくらいでしょうか。

View

// views/posts/search.ctp
<h2>記事検索</h2>
<?php
    echo $form->create('Post', array(
        'url' => array_merge(array('action' => 'search'), $this->params['pass']),
        'name' => 'PostSearchForm'
    ));
    echo $this->Form->input('Tag', array(
        'type' => 'select',
        'multiple' => 'checkbox',
        'options' => $tags,
    ));
    echo $form->submit('検索', array('div' => false));
    echo $form->end();
?>
<?php foreach ($posts as $post): ?>
<dl>
    <dt>タイトル</dt>
        <dd><?php e(h($post['Post']['title'])); ?>&nbsp;</dd>
    <dt>本文</dt>
        <dd><?php e(h($post['Post']['body'])); ?>&nbsp;</dd>
</dl>
<?php endforeach; ?>

    <p>
    <?php
    echo $this->Paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
    ));
    ?>  </p>

    <div class="paging">
        <?php echo $this->Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?>
     |  <?php echo $this->Paginator->numbers();?>
 |
        <?php echo $this->Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?>
    </div>

Search Plugin は情報があまり多くないのが欠点ですが、こういう検索処理を一から作る手間や、paginate との連携を考えると手放す事ができないプラグインですね。

参考サイト

CakeDC’s search at master - GitHub
http://github.com/CakeDC/search

[CakePHP]タグの絞込み機能 | HappyQuality
http://www.happyquality.com/2009/02/13/759.htm