friendsofcake/search を使っての検索は非常に楽なんですが、いろいろわからない点が多いんですよね。
カラムを連結して検索する要件があたのですが、手間取ったので記事にしました。
CakePHP2の場合
CakePHP2の場合は非常に簡単です。
virtualfieldsという機能があって、これを使えばテーブルにカラムがあるのと同じように扱えばOKです。
CakePHP3以降は?
残念なことにCakePHP3以降ではvirtualfieldsが亡くなってしまいました。
代わりに仮想プロパティーというものになりました。
ただし、これはオブジェクトに対してしか使えず、ここで定義したプロパティーは検索には使えませんので、今回の要件には合いません。
ということで、SQLのconcat関数で結合してあげてから検索するわけですが、これがまた曲者です。
$query = $this->Users->find() ->select(['full_name'=>$query->func()->concat(['last_name'=>'identifier','first_name'=>'identifier'])],false);
これで、データを取得するとfull_nameというプロパティーができてて、意図通りに名前がつながった形で取得できるのですが、これを検索キーに使等とすると、どうやってもできない。
勝手にカラム名が変換されてしまって意図したとおりのSQL文が生成できないんです。
cakebookにはなさそうなんですが、Where句の中でつなげてあげてやるといいみたいです。
$query = $this->Users->find();
$query = $query->where(function ($exp, $query) use ($keyword) {
$concat = $query->func()->concat([
'last_name' => 'identifier',
'first_name' => 'identifier'
]);
return $exp->like($concat, $keyword);
});
というわけで、これをfriendsofcake/searchで実現するにはどうしたらいいか?
こんな感じで実装しました。
//Tableクラスの中のinitialize()メソッドの中で…
$this->searchManager()
->add('keywords', 'Search.Callback',[
'callback' => function (Query $query, array $args, \Search\Model\Filter\Base $filter) {
$query->where([ //return で返す?
function ($exp,$query) use ($args){
$concat = $this->find()->func()->concat([
'last_name'=>'identifier',
'first_name'=>'identifier',
'last_name_kana'=>'identifier',
'first_name_kana'=>'identifier',
'email'=>'identifier',
]);
//複数キーワードのAND検索に対応
$keywords = explode(" ",mb_convert_kana($args['keywords'],'s'));
foreach($keywords as $keyword)
$query->like($concat,"%{$keyword}%"); //return で返す?
}
]);
}
]);
Search.Like を使えば複数キーワードを指定できるのですがconcatを使う場合は無理みたいなので、自前で分解してlikeメソッドに入れています。
あと、ネットを検索すると、無名関数の戻り値としてQueryオブジェクトを返すように書いてあるのですが、そのようにすると型が違うというようなエラーになってしまいます。おそらくPHPのバージョンで型が厳密になったためと思います。
追記
concat()で追加したフィールドがnull可になっていて値にNULLが入っていると、そのレコード自体が検索対象にならなくなってしまいます。
この場合は、以下のようにIFNULL()を追加してあげる必要があるようです。
'IFNULL(last_name,"")'=>'identifier',
