前提・実現したいこと
複数タグ検索機能を実装したい。
検索フォームで "#"を先頭に持つキーワードをタグと認識し、検索。
2つ以上タグがあったとしても絞り込みで検索できるようにしたい。
https://tic40.hatenablog.com/entry/2017/02/25/135528
上記の記事を参考に、下記のMySQL文で複数タグ検索が可能とわかった。
SELECT * FROM posts JOIN post_tags ON posts.id = post_tags.post_id JOIN tags ON post_tags.tag_id = tags.id WHERE tags.name IN ('aaa','bbb') GROUP BY posts.id HAVING COUNT(posts.id) = 2;
これを参考にしながら、Laravelのクエリビルダに変換して、実装したい。
環境
Laravel6
DB MySQL
####モデル
postsテーブル
id | title | body |
---|
post_tagsテーブル
post_id | tag_id |
---|
tagsテーブル
id | name |
---|
post.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class post extends Model { protected $fillable = [ 'title', 'body', 'user_id' ]; public function user() { return $this->belongsTo('App\User'); } public function tags() { return $this->belongsToMany('App\Models\Tag', 'post_tags'); } }
tag.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class tag extends Model { protected $fillable = [ 'name' ]; public function posts() { return $this->belongsToMany('App\Models\Post', 'post_tags'); } }
post_tag.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class post_tag extends Model { protected $fillable = [ 'post_id', 'tag_id' ]; }
実装の試み
まずは純粋にSQLで取得できる確認をした。
https://tic40.hatenablog.com/entry/2017/02/25/135528
上記の記事より下記のMySQL文で複数タグ検索が可能とわかった。
mysql> SELECT item.* -> FROM item -> JOIN item_to_tag AS itt ON item.id = itt.item_id -> JOIN tag ON itt.tag_id = tag.id -> WHERE tag.name IN ('tag4','tag5') -> GROUP BY item.id -> HAVING COUNT(item.id) = 2;
これを元に自分のDBに合わせて作成。
SELECT * FROM posts JOIN post_tags ON posts.id = post_tags.post_id JOIN tags ON post_tags.tag_id = tags.id WHERE tags.name IN ('aaa','bbb') GROUP BY posts.id HAVING COUNT(posts.id) = 2;
無事に取得できたので、これをクエリビルダに変換する
###クエリビルダに変換する
検索キーワードは下記の処理で、タグのみ取得します
準備:検索キーワードを取得
php
1 2$keyword = $request->input('search'); 3 4//全角スペースを半角スペースに変換 5$keyword_space_half = mb_convert_kana($keyword, 's'); 6 7//正規文を利用して、#を含むキーワードを取得 8preg_match_all('/#([a-zA-z0-90-9ぁ-んァ-ヶ亜-熙]+)/u', $keyword, $match); 9//->$match[0]で”#”ありの検索時のタグ名 10//->$match[1]が”#”なしタグの名前。 11 12 13//半角スペースでキーワードをsplitで分割 14$keywords = preg_split('/[\s]+/', $keyword_space_half); 15 16 17//$match[0]が#ありのキーワード -> これを削除して、純粋なキーワードだけにする。 18$no_tag_keywords = array_diff($keywords, $match[0]); 19 20//$match[1]が”#”なしのtagの名前 21$tags = $match[1]; 22
本題のクエリビルダを記述
先ほど定義した$tags
を使って、タグ検索する。
$query = DB::table('posts'); $query ->join('post_tags', 'posts.id', '=', 'post_tags.post_id') ->join('tags', 'post_tags.tag_id', '=', 'tags.id') ->whereIn('tags.name', $tags) ->groupBy('posts.id') ->having(count('posts.id'), '=', [count($tags)]);
下記の実行したいSQLとほぼ同じにしているはずです。
SELECT * FROM posts JOIN post_tags ON posts.id = post_tags.post_id JOIN tags ON post_tags.tag_id = tags.id WHERE tags.name IN ('tag1','tag2') GROUP BY posts.id HAVING COUNT(posts.id) = 2;
tag1
とtag2
はタグ名の一例なので、タグのnameが入っている$tags
を入れています。
HAVING COUNT(posts.id) = 2;
の2は、検索時のタグの数なので、count($tags)
で値を出しています。
しかし、->having(count('posts.id'), '=', [count($tags)]);
でエラーが発生します
エラー
実際のエラー文
count(): Parameter must be an array or an object that implements Countable
日本語:パラメータは配列か Countable を実装したオブジェクトでなければなりません。
count()の中にarrayを入れるように指示があることから、配列でないことが問題だとわかります。
しかし、ここからどうすればよいかわかりません。
SQL文を再現したつもりでしたが、うまくいってません。
どのように記述するべきか、ご教授お願いします。
よろしくお願いします。
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/10/04 08:44
2020/10/04 08:52
2020/10/04 08:56