サーチプラグインでカラムを連結して検索したい

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',
タイトルとURLをコピーしました