CakePHP3のfriendsofcake/searchでブックマークチュートリアルのタグ検索を実装
2019/07/22
CakePHP3の「ブックマークチュートリアル」のタグ検索を検索プラグイン「friendsofcake/search」で実装する方法を解説
「friendsofcake/search」は CakePHP3で使われる検索処理のプラグイン
この記事では、CakePHP3のオフィシャルの Cookbookの中にある「『ブックマークチュートリアル』で『タブを指定してブックマークを取得』」の処理を、検索プラグイン「friendsofcake/search」を利用して実装する方法を解説しています。
Cookbook・ブックマークチュートリアル
https://book.cakephp.org/3.0/ja/tutorials-and-examples/bookmarks/intro.html
「ブックマークチュートリアル」にあるような、「ブックマーク」と「タグ」を関連付けているテーブルを検索対象として検索するイメージの検索です。
検索対象のコードが検索対象のテーブルではなく、関連テーブル(アソシエーションしたテーブル)にある 1対N の項目に対する検索です。
(1対1のアソシエーションなどで contain句で接続できるテーブルの場合は、もっと単純に組み込むことができます。その方法は「CakePHP3の検索プラグイン「friendsofcake/search」の様々な検索の仕方の実装方法」で解説していますので、こちらを参考にしてみてください。)
「friendsofcake/search」の設置方法など、基本的な使い方については下記の記事を参考にしてください。
CakePHP3の検索プラグイン「friendsofcake/search」の設置方法・CakePHP3.6対応
また、部分一致検索、完全一致検索、以上/以下などの値の比較検索、複数項目の検索など、「friendsofcake/search」で実装できる様々な検索方法についての解説については下記に記事を書いています。
CakePHP3の検索プラグイン「friendsofcake/search」の様々な検索の仕方の実装方法
また、CakePHP3のオフィシャルの Cookbookの中にある「『ブログチュートリアル・パート3』で『ツリーカテゴリーの作成(ツリービヘイビアの利用)』」で作成されるツリー構造のカテゴリを、自身のカテゴリを含む子階層のカテゴリをすべて対象として実施する検索を、検索プラグイン「friendsofcake/search」を利用して実装方法についての解説は、下記の記事を参考にしてください。
CakePHP3のfriendsofcake/searchでツリーカテゴリーの子階層も含めて検索する方法
ちなみに、「friendsofcake/search」の設置方法は、CakePHP3.6になったときに少し変わっていますので、CakePHP3.6がリリースされた 2018年4月14日以前に書かれた記事では動作しないものもあるように感じます。
「friendsofcake/search」の実装をするときは、記事が書かれた日付を確認しつつ参考にしたほうが良さそうです。
この記事は、CakePHP3.7で動作確認しながら書いています。
ブックマークチュートリアルに「friendsofcake/search」を使ってタグ検索を実装する
ブックマークチュートリアルの環境構築
今回構築する検索処理ですが、使用するテーブル構成やプログラムは、下記の「ブックマークチュートリアル」の内容を利用しています。
まずは、下記のチュートリアルに従って環境を構築してください。
Cookbook・ブックマークチュートリアル
https://book.cakephp.org/3.0/ja/tutorials-and-examples/bookmarks/intro.html
「ブックマークチュートリアル」のとおりに環境を構築し、動作しているところに検索プラグイン「friendsofcake/search」を導入することを想定しています。
そのため、テーブル名やカラム名について特に説明はしません。
「ブックマークチュートリアル」の記事で確認をしてください。
「friendsofcake/search」のインストールとロード
「friendsofcake/search」のインストールまではできているものとします。
CakePHP3の検索プラグイン「friendsofcake/search」の設置方法・CakePHP3.6対応
具体的には、上記の記事の
「1.Composerを使って「friendsofcake/search」をインストール」
「2.「friendsofcake/search」をロード」
まではできている想定です。
まだの場合は、上記の記事を参考に実行してください。
3.Model(Table)に検索処理を追加
Model(Table)にビヘイビアと検索条件の追加を行います。
/src/Model/Table/BookmarksTable.php
に下記の処理を追加します。
まず最初に、下記の use句を追加します。
「BookmarksTags」テーブルにアクセスするために使用します。
1 |
use Cake\ORM\TableRegistry; |
さらに下記の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public function initialize(array $config) { : (既存の処理) : // ビヘイビア(friendsofcake/search)の追加 $this->addBehavior("Search.Search"); // 検索条件の追加 $this->searchManager() ->like("search01",["before"=>true,"after"=>true,"field"=>"title"]) ->add("search02","Search.Callback",[ "callback"=>function(Query $query, array $args){ $bookmarksTags = TableRegistry::getTableLocator()->get("BookmarksTags"); $bookmarks = $bookmarksTags->find()->where(["tag_id in "=>$args["search02"]])->distinct(["bookmark_id"]); $bookmarksArray = []; foreach($bookmarks as $bookmarksVal){ $bookmarksArray[] = $bookmarksVal->bookmark_id; } return $query->where(["Bookmarks.id IN"=>$bookmarksArray]); } ]); |
「CakePHP3の検索プラグイン「friendsofcake/search」の設置方法・CakePHP3.6対応」の記事をベースに記事を書いていますので、最初に tableファイルの編集が来ていますが、ここが最重要ポイントです。
「タグ」を検索するポイントは、「callback句」を利用して、まず「bookmarks_tags」テーブルの中から該当する「bookmark_id」を取得することと、その「bookmark_id」を「distinct()」句を使ってユニークにするところです。
(※「distinct()」句を使わなくても問題ありません。「distinct()」句を使わなかった場合は、「$bookmarksArray」の配列の中に重複した「bookmark_id」が入ることになりますが、検索処理自体は正しい結果を得ることができます。)
このポイントが理解できれば、ここ以外は普通に「friendsofcake/search」を設定する方法とほぼ変わりがありません。
※更新情報(2019年07月22日)
上記のサンプルソースですが、デバッグが十分ではなく、正しく動作していなかったことが確認されましたので、全面的な修正を行いました。
4.Controllerに検索処理を追加
Controllerに検索処理を追加します。
/src/Controller/BookmarksController.php
に下記の処理を追加します。
まず最初に、下記の use句を追加します。
1 |
use Cake\ORM\TableRegistry; |
「tags」テーブルにアクセスするために使用します。
さらに下記の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class TestsController extends AppController { // ページング設定の追加 public $paginate = [ "limit" => 100 ] ]; public function initialize() { parent::initialize(); // 検索処理のロードの追加 $this->loadComponent("Search.Prg", [ "actions" => ["index"] // ここで検索するアクションを配列で指定 ]); } public function index() { // Bakeしたときに生成された元の処理 // $this->paginate = [ // 'contain' => ['Users'] // ]; // $bookmarks = $this->paginate($this->Bookmarks); // 上記の元の処理を下記に変更 $query = $this->Bookmarks ->find("search",["search"=>$this->request->getQuery()]) ->contain(["Users","tags"]); $bookmarks = $this->paginate($query); $this->set(compact("bookmarks")); // Tagsテーブルを配列として取得 $this->Tags = TableRegistry::getTableLocator()->get("tags"); $tagsList = $this->Tags->find("list",["keyField"=>"id","valueField"=>"title"])->all()->toArray(); $this->set(compact("tagsList")); } |
Controllerの処理は、基本的な「friendsofcake/search」の使い方と違う点はありません。
ただ、「タグ」をチェックボックスとして編集するために、「Tagsテーブルを配列として取得」の処理を追加しています。
5.Templateに検索条件のテキストボックスを追加
最後に、Templateに検索条件を入力するテキストボックスを追加します。
/src/Template/Bookmarks/index.ctp
に下記の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 |
<div> <?php echo $this->Form->create(null, ["valueSources"=>"query"]); echo $this->Form->input("search01",["label"=>"title"]); echo $this->Form->input("search02",["type"=>"select", "multiple"=>"checkbox", "options"=>$tagsList, "label" => "Tags"]); echo $this->Form->button(__("Search"), ["type"=>"submit"]); echo $this->Form->end(); ?> </div> |
タグを検索するチェックボックスは、5~8行目の記述で追加します。
チェックボックス自体は「”type”=>”checkbox”」と記述しても表示することができます。
ですが、検索項目のチェックボックスとして機能させるためには、「”type”=>”select”,”multiple”=>”checkbox”」と記述する必要があります。
また、チェックボックスの選択肢は「”options”=>$tagsList」で指定します。
「$tagsList」は、Controllerで配列として生成しています。
加えて、検索した「タグ」の項目も一覧表示する必要がありますので、一覧表は下記のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<table cellpadding="0" cellspacing="0"> <thead> <tr> <th scope="col"><?= $this->Paginator->sort('id') ?></th> <th scope="col"><?= $this->Paginator->sort('user_id') ?></th> <th scope="col"><?= $this->Paginator->sort('title') ?></th> <th scope="col"><?= $this->Paginator->sort('tag') ?></th> <th scope="col"><?= $this->Paginator->sort('deleted') ?></th> <th scope="col"><?= $this->Paginator->sort('created') ?></th> <th scope="col"><?= $this->Paginator->sort('modified') ?></th> <th scope="col" class="actions"><?= __('Actions') ?></th> </tr> </thead> <tbody> <?php foreach ($bookmarks as $bookmark): ?> <tr> <td><?= $this->Number->format($bookmark->id) ?></td> <td><?= $bookmark->has('user') ? $this->Html->link($bookmark->user->id, ['controller' => 'Users', 'action' => 'view', $bookmark->user->id]) : '' ?></td> <td><?= h($bookmark->title) ?></td> <td><?php $tagArray = []; foreach($bookmark->tags as $tagVal){ $tagArray[] = $tagVal["title"]; } echo implode("、",$tagArray); ?> </td> <td><?= h($bookmark->deleted) ?></td> <td><?= h($bookmark->created) ?></td> <td><?= h($bookmark->modified) ?></td> <td class="actions"> <?= $this->Html->link(__('View'), ['action' => 'view', $bookmark->id]) ?> <?= $this->Html->link(__('Edit'), ['action' => 'edit', $bookmark->id]) ?> <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $bookmark->id], ['confirm' => __('Are you sure you want to delete # {0}?', $bookmark->id)]) ?> </td> </tr> <?php endforeach; ?> </tbody> </table> |
テンプレートを更新すると、下記の様なイメージになります。
以上になります。
検索プラグイン「friendsofcake/search」の解説記事は、基本的な導入方法にとどまるものがほとんどで、様々な検索方法について解説している記事はほぼありません。
参考になる記事が少ないという点で実装が難しいのですが、いざ分かってみれば、実装方法自体は難しくありません。
やはり、効率的な開発を行うには汎用的な処理はプラグインを活用する方がいい、と改めて感じました。
CakePHP3の関連記事
CakePHP4のCSS、JavaScript、画像のブラウザへのキャッシュをコントロールするCakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介
CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説
CakePHP3でPHP Simple HTML DOM Parserを使ってスクレイピングする方法
CakePHP3のInsert On Duplicate Key Update(upsert)構文を解説・バルク処理も
CakePHP3の1対多での連携を中間テーブルを使った多対多の連携に変更するときの手順
CakePHP3でデフォルトのソート条件を設定してユーザの選択肢たソート条件を有効にする方法
CakePHP3で Ajaxを使う方法の解説。3.6以降対応。Successとthenの両方を解説。
CakePHP3でパンくずの指定は HTMLヘルパーを使って指定する方法を解説
CakePHP3にOGPをfetch、asignを利用してテンプレートごとに指定する方法を解説
その他の「CakePHP3」に関する記事一覧
GoogleAdwords
GoogleAdwords
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
cakephp3 カスタムバリデーションを簡易的に実装する方法
CakePHP3の独自のバリデーションをテーブルクラス内に簡単に記述する方法を解説。他のテーブルクラスでは使えないが、記述する量は少なく実装できるため、他で使わない処理を書くのには便利。
-
CakePHP4 でコマンドプログラム(シェルプログラム)を作成する方法解説
CakePHP4でバッチ処理を行うためのコマンド・シェルの実装方法について解説。bakeでテンプレートファイルを作成し、「execute()」に処理を記述する方法を解説。
-
CakePHP3で他のテーブルのマスタテーブルからセレクトボックス(プルダウンリスト)を作る
他のテーブルのマスタのレコードからプルダウンリストを作成し、選択できるようにするサンプルプログラムと解説。ORMの設定によりデータベースの値を取得し、配列を作成し optionsに与える。
-
CakePHP4のcake cache clear_allでPermission deniedはパーミッションの変更が必要
CakePHP4でキャッシュをクリアするコマンドcake cache clear_allでPermission deniedのエラーが出るのはcakeにパーミッションが足りないとき。その対処方法を解説。
-
CakePHP3でPHPExcelを使ってエクセルファイルを生成、出力する方法
CakePHP3でPHPExcelを利用してエクセルを編集、出力するサンプルソース+解説。PHPExcelのインストール方法の解説からファイル保存とダウンロードの方法なども解説。
-
CakePHP3でデータを保存する save()で発生するエラーを確認する方法を解説
CakePHP3でデータ保存処理のログを取得する方法。save()では true、falseの戻り値しか取得できないが、saveOrFail()と try…catch文を使いエラーログ、エンティティを取得し、不具合の解析を行う。
-
CakePHP3でシェルを作成しコマンドラインから実行・CakePHP2との違い
CakePHP3のシェルスクリプトを作成し、コマンドラインから実行する方法を解説。複数単語をつなげる場合の対応方法がCakePHP2より制限が厳しくなったのでCakePHP3の命名規則の確認が必要だ。
-
CakePHPのFlashエラーは出るが入力項目ごとのメッセージが出ないエラーの原因
Bakeして自動生成した入力フォーム処理を元に少し処理を追加したら、入力エラーがあってもエラーメッセージが表示されなくなった。原因はリダイレクトの処理にあった。
-
CakePHP2の検索Plugin CakeDC/Searchで重複を省くgroup by(distinct)の実装方法
CakePHP2の検索プラグイン「CakeDC/Search」で、重複レコードを省くgroup by、distinctを使う方法についての解説。設定する場所はpaginatorの条件とするので、find()関数と同じ。
-
MySQL、CakePHP 2.3で「tinyint(1)」の Boolean型の動作を再確認
MySQL+CakePHPの環境で「tinyint(1)」を利用する際の動作を検証。「tinyint(1)」の Boolean型について CakePHPでは自動処理が実施されていることを確認しました。