モーグルとカバとパウダーの日記

モーグルやカバ(EXカービング)山スキー(BC)などがメインの日記でした。今は仕事のコンピュータ系のネタが主になっています。以前はスパム対策関連が多かったのですが最近はディープラーニング関連が多めです。

CakePHPのSearchプラグインでハマったところ

最近CakePHPを使って開発をしてるのですが、なにかやろうとするたびにとにかく色んな所でハマっています。
今回はSearchプラグインという、CakePHP使う上では結構必須と言われてるようなプラグインの使い方でハマってしまったのでそのまとめです。


Searchプラグインの使い方については、このエントリーが大変丁寧に紹介されており、非常に参考になりました。
ので、基本的にはこちらの内容にしたがって導入すれば良いです。


Searchプラグインを使ってCakePHPに検索を実装する | mawatari.jp
http://mawatari.jp/archives/introduction-of-cakedc-search-plugin-for-cakephp

Searchプラグインがどう動くのか概要説明

が、これだけだと自分は結構分からなくてハマるところがありました。
それはSearchプラグインがどうやって動くか、という大前提みたいなものの理解があればもっと早く解決出来たのに…と思ったので、まずそれを説明します。


1. どのフィールドをどんな方法で検索するかはモデルに設定しておく

検索時に検索の仕方を指定するのではなく、モデルの設定で、このフィールドに対しては部分一致で検索する、とかの条件を先に設定しておくという感じになっています。
だから実際に検索する所でのコードには何も書かなくて良くなっています。


2. 検索するフィールドは入力フォームの名前で自動的に決まる
自分は最初、フォームから入力された内容を自分でSearchプラグインに渡してやる必要があると思っていました。(実際にはそうなってるのですが)
が、基本的な使い方としては、入力フォームの名前を検索フィールド名にしておくと、勝手にそのフィールド名に対しての検索として検索してくれます。
だから、単純な検索であればフォームの名前を指定するだけで検索ができます。


3. 検索パラメータはURLに書かれてリダイレクトされてから処理される
検索は直接行われるのではなく、一旦下記のようなURLに変換されてリダイレクトされてから行われます。

http://localhost/sample/search/category_id:1/title:CakePHP

そのため、フォームからの入力をそのまま通常のやり方で読み込もうとしてもできません

具体的な実装方法と内容説明

自分は、あるcategory_idのものに対してキーワード検索をするものを実装しようとしていました。
そして、category_idはフォームからの入力ではなく、メソッドで固定にしたい、とします。
その場合、下記のようなコードで動きます。


モデル

class Item extends AppModel {
    public $filterArgs = array(
        'category_id' => array('type' => 'value'),
        'title' => array('type' => 'like'),
    );


コントローラー

$category_id = 1;
$preConditions = array('category_id' => $category_id);
$this->Prg->commonProcess();
$searchConditions = $this->Prg->parsedParams();
$searchConditions = array_merge($searchConditions, $preConditions);
$this->paginate = array(
    'conditions' => $this->Item->parseCriteria($searchConditions),
);
$this->set('Items', $this->paginate());


ビュー

echo $this->Form->create('Item');
echo $this->Form->text('title');
echo $this->Form->end('検索');


このように、コントローラーで実際に検索しているコード中には、どのフィールドに対しての検索(ここでは「title」フィールドを部分一致)かは出て来ません。


コントローラーでcommonProcess()というメソッドを呼んでいますが、実はここでフォームに入力されたパラメータの解析が行われ、3.で書いたようなURLに変換されてリダイレクトされるようになっています。
なので、自前で通常のようにフォームからの入力を取得しようとすると、うまくいかずにハマります。


URLの形で与えられたパラメータを取得するのにparsedParams()(バージョン2.3以降。それまでは$this->passedArgs)で行われています。
そこで得られるパラメータは

http://localhost/sample/search/category_id:1/title:CakePHP

のようなURLだと

array(
    'category_id' => 1,
    'title' => 'CakePHP',
)

のようになっています。


ここではcategory_idを固定にしたかったため、array_mergeでcategory_idを決め打ちにしたものを作っているわけです。

検索フォームに勝手に「required」属性がついて必須項目にされる問題

で、だいたいはこれで良かったのですが、次に複数の検索項目で検索させようとした時に、どちらの検索項目も入力しないと

このフィールドは入力必須です

と言われてしまう、という問題が起きました。


これはHTML5で「required」というの新しい属性が付けられるようになったもので、CakePHP2.3のFormヘルパーから、モデルのバリデーションで必須項目になっているフィールドにはrequiredが勝手につくようになっている、とのことでした。


CakePHP2.3からinputタグにhtml5のrequired属性がつくようになった - cakephperの日記(CakePHP, MongoDB)
http://d.hatena.ne.jp/cakephper/20130211/1360589926


普通の入力フィールドの場合はこの動作で良いのでしょうが、Searchプラグインで使っている場合、自動的にrequiredがついてしまうのは困ります。
この場合「novalidate」という指定をしてフォームを作成すると良いとのことです。

echo $this->Form->create('Item', array('novalidate' => true)); 

検索フォームを空にしたことが反映されない問題

各フィールドの検索条件を設定する filterArgs の設定に「empty」オプションというものがあり、これを true にしておくと入力のなかった余計なフィールドの情報を消してURLをスッキリさせることが出来ます。
下記のように設定します。

class Item extends AppModel {
    public $filterArgs = array(
        'category_id' => array('type' => 'value', 'empty' => true),
        'title' => array('type' => 'like', 'empty' => true),
    );


これを設定すると例えば「title」の検索入力がなかった場合に

http://localhost/sample/search/category_id:1/title:

となるものを

http://localhost/sample/search/category_id:1

と余計なフィールドを削除したURLにしてくれるというものです。


しかしこれは結構大きな副作用を起こします。

一度、ある検索フィールドに検索条件を入れて検索し、再度その検索内容を消して他の検索フィールドを設定して検索しても、その検索条件を消してくれないのです。

この例で言うなら一度 category_id の設定をして検索してしまうと、category_id を消して検索しようとしても、前の検索で入力した category_id が復活してきて消すことが出来なくなります。
この場合、URLに残っている検索条件を消さないと消えてくれません。


なので「empty」オプションはあまり必要性がないのなら使わないほうがベターだと思います。