CakePHP3のfriendsofcake/searchでツリーカテゴリーの子階層も含めて検索する方法
2020/01/02
CakePHP3の「ブログチュートリアル・パート3」のツリーカテゴリー(ツリービヘイビア)の子階層も含めたカテゴリ検索を検索プラグイン「friendsofcake/search」を使って実装する方法を解説
「friendsofcake/search」は CakePHP3で使われる検索処理のプラグイン
この記事では、CakePHP3のオフィシャルの Cookbookの中にある「『ブログチュートリアル・パート3』で『ツリーカテゴリーの作成(ツリービヘイビアの利用)』」で作成されるツリー構造のカテゴリを、自身のカテゴリを含む子階層のカテゴリをすべて対象として実施する検索を、検索プラグイン「friendsofcake/search」を利用して実装する方法を解説しています。
Cookbook・ブログチュートリアル パート3
https://book.cakephp.org/3/ja/tutorials-and-examples/blog/part-three.html
Cookbook・Tree(ツリービヘイビア)
https://book.cakephp.org/3.0/ja/orm/behaviors/tree.html
ツリービヘイビアを利用すると、下記のようなツリー構造のカテゴリを簡単に実装することができます。
1 2 3 4 5 6 7 |
カテゴリ1 ┣カテゴリ1-1 ┃ ┣カテゴリ1-1-1 ┃ ┗カテゴリ1-1-2 ┗カテゴリ1-2 ┣カテゴリ1-2-1 ┗カテゴリ1-2-2 |
上記のようなツリー構造のカテゴリがある場合、「カテゴリ1-1」で検索すると、「カテゴリ1-1」だけではなく、そのカテゴリに属する子カテゴリも含めて下記の部分が検索にヒットさる、というのが今回の記事の趣旨です。
1 2 3 |
┣カテゴリ1-1 ┃ ┣カテゴリ1-1-1 ┃ ┗カテゴリ1-1-2 |
「friendsofcake/search」の設置方法など、基本的な使い方については下記の記事を参考にしてください。
CakePHP3の検索プラグイン「friendsofcake/search」の設置方法・CakePHP3.6対応
また、部分一致検索、完全一致検索、以上/以下などの値の比較検索、複数項目の検索など、「friendsofcake/search」で実装できる様々な検索方法についての解説については下記に記事を書いています。
CakePHP3の検索プラグイン「friendsofcake/search」の様々な検索の仕方の実装方法
また、CakePHP3のオフィシャルの Cookbookの中にある「『ブックマークチュートリアル』で『タブを指定してブックマークを取得』」の処理を、検索プラグイン「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/blog/blog.html
https://book.cakephp.org/3.0/ja/tutorials-and-examples/blog/part-two.html
https://book.cakephp.org/3.0/ja/tutorials-and-examples/blog/part-three.html
Cookbook・ツリービヘイビア
https://book.cakephp.org/3.0/ja/orm/behaviors/tree.html
「ブログチュートリアル」のとおりに環境を構築し、動作しているところに検索プラグイン「friendsofcake/search」を導入することを想定しています。
そのため、テーブル名やカラム名について特に説明はしません。
「ブログチュートリアル」の記事で確認をしてください。
「friendsofcake/search」のインストールとロード
「friendsofcake/search」のインストールまではできているものとします。
CakePHP3の検索プラグイン「friendsofcake/search」の設置方法・CakePHP3.6対応
具体的には、上記の記事の
「1.Composerを使って「friendsofcake/search」をインストール」
「2.「friendsofcake/search」をロード」
まではできている想定です。
まだの場合は、上記の記事を参考に実行してください。
3.Model(Table)に検索処理を追加
Model(Table)に、use句と、ビヘイビア、検索条件の追加を行います。
作業対象ファイルは以下になります。
/src/Model/Table/ArticlesTable.php
まず、「Categories」テーブルを呼び出すために下記の use句を追加します。
1 |
use Cake\ORM\TableRegistry; |
続けて、「ビヘイビア(friendsofcake/search)の追加」「検索条件の追加」を行います。(「ブログチュートリアルで追加した処理」の部分は、ブログチュートリアルの中ですでに追加してあるはずものです。)
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 |
public function initialize(array $config) { : (既存の処理) : // ブログチュートリアルで追加した処理 $this->belongsTo('Categories', [ 'foreignKey' => 'category_id', ]); // ビヘイビア(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){ $categories = TableRegistry::getTableLocator()->get("Categories"); $child = $categories->find("children",["for"=>$args["search02"]]); $childArray[] = $args["search02"]; foreach($child as $val){ $childArray[] = $val->id; } return $query->where(["category_id IN"=>$childArray]); } ]); |
「CakePHP3の検索プラグイン「friendsofcake/search」の設置方法・CakePHP3.6対応」の記事をベースに記事を書いていますので、最初に Tableファイルの編集が来ていますが、「friendsofcake/search」では、検索条件を Tableファイルに記述しますので、ここが最重要ポイントです。
ポイントは、「callback句」を利用することと、「$categories->find("children",["for"=>$args["search02"]]);
」で子階層のカテゴリの IDを取得するところです。
ノードの子孫のフラットなリストを取得する方法($categories->find("children",["for"=>1]);
)として、Cookbookの割とはじめの方に書いてあります。
このポイントが理解できれば、ここ以外は普通に「friendsofcake/search」を設定する方法とほぼ変わりがありません。
4.Controllerに検索処理を追加
Controllerに検索処理を追加します。
/src/Controller/ArticlesController.php
に下記の処理を追加します。
まず、「Categories」テーブルを呼び出すために下記の use句を追加します。
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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class TestsController extends AppController { // ページング設定の追加 public $paginate = [ "limit" => 100 ] ]; public function initialize() { parent::initialize(); // 検索処理のロードの追加 $this->loadComponent("Search.Prg", [ "actions" => ["index"] // ここで検索するアクションを配列で指定 ]); } public function index() { // Cookbookに従って記述した処理 // $query = $this->Articles->find()->contain(["Categories"]); // $articles = $this->paginate($query); // 上記の元の処理を下記に変更 $query = $this->Articles ->find("search",["search"=>$this->request->getQuery()]) ->contain(["Categories"]); $articles = $this->paginate($query); $this->set(compact('articles')); // Categoriesテーブルを配列として取得 $this->Categories = TableRegistry::getTableLocator()->get("Categories"); $categoriesList = $this->Categories->find("treeList",["keyField"=>"id","valueField"=>"name","spacer"=>"--"]])->order(["lft"=>"ASC"])->all()->toArray(); $this->set(compact("categoriesList")); } |
Controllerの処理は、基本的な「friendsofcake/search」の使い方と違う点はありません。
ただ、「カテゴリ」をセレクトボックスとして編集するために、「Categoriesテーブルを配列として取得」の処理を追加しています。
この中で、ツリービヘイビア特有の項目が「$this->Categories->find("treeList")
」の部分です。
セレクトボックスなどを作成するために必要な配列を取得するときは「find("list")
」としますが、下記のようにツリーリストにしたい場合には「find("treeList")
」を使用します。
1 2 3 |
カテゴリ1 --カテゴリ1-1 ----カテゴリ1-1-1 |
また、「order(["lft"=>"ASC"])
」を入れることで、カテゴリの親子関係を維持した順番に並べることができます。
また、小カテゴリの階層化のための文字として、上記の設定では「--(全角ハイフン 2つ)」を指定しています。
これは、「"spacer"=>"--"
」で指定しているもので、好きなものに変更することができます。
これを指定しない場合は、デフォルトとして設定されている「_(半角アンダースコア)」が使われます。
5.Templateに検索条件のセレクトボックスを追加
最後に、Templateに検索条件を入力するセレクトボックスを追加します。
/src/Template/Articles/index.ctp
に下記の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 |
<div> <?php echo $this->Form->create(null, ["valueSources"=>"query"]); echo $this->Form->input("search01",["label"=>"title"]); echo $this->Form->input("search02",["type"=>"select", "options"=>$categoriesList, "label" => "Categories"]); echo $this->Form->button(__("Search"), ["type"=>"submit"]); echo $this->Form->end(); ?> </div> |
タグを検索するセレクトボックスは、5~7行目の記述で追加します。
また、セレクトボックスの選択肢は「”options”=>$categoriesList」で指定します。
「$categoriesList」は、Controllerで配列として生成しています。
また、Cookbookのとおりに実行しただけでは、検索結果の一覧表示に「カテゴリー」が表示されません。
検索結果が正しいかどうかの確認のためにも、検索結果の一覧表示に「カテゴリー」を追加したほうがいいでしょう。
以上になります。
検索プラグイン「friendsofcake/search」の検索選択肢をチェックボックスで用意する
今回この解説では、カテゴリーの選択肢はセレクトボックスで 1つだけ選択する方法を採っています。
ですが、チェックボックスにして複数選択にする方式に変更する方法は簡単です。
まず、「/src/Template/Articles/index.ctp」の検索フォームエリアを下記のように変更します。
変更点は、6行目の「"multiple"=>"checkbox",
」の追加です。
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"=>$categoriesList, "label" => "Categories"]); echo $this->Form->button(__("Search"), ["type"=>"submit"]); echo $this->Form->end(); ?> </div> |
続いて、「ArticlesTable.php」の「initialize()」の中の「検索条件の追加」を下記のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public function // 検索条件の追加 $this->searchManager() ->like("search01",["before" => true,"after" => true,"field"=>"title"]) ->add("search02","Search.Callback",[ "callback"=>function(Query $query, array $args){ $childArray = []; foreach($args["search02"] as $tmp){ $childArray[] = $tmp; $child = $categories->find("children",["for"=>$tmp]); foreach($child as $val){ $childArray[] = $val->id; } } return $query->where(["category_id IN"=>$childArray]); } ]); |
「$args["search02"]
」として受け取った値が配列になっていますので、それを foreach()文で取り出す、という部分が増えています。
検索プラグイン「friendsofcake/search」で、チェックボックスを利用して検索処理を実装する方法としては、下記の記事にも解説をしていますので、あわせて参考にしてみてください。
CakePHP3のfriendsofcake/searchでブックマークチュートリアルのタグ検索を実装
Cookbookのブログチュートリアル・パート3の不具合点
Cookbookのブログチュートリアル・パート3は、解説のとおりにソースコードを修正しても、Articlesの追加、更新でカテゴリの情報が正しく保存されません。
(カテゴリIDが常に「0」で保存されてしまいます。)
これは、「/src/Model/Entity/Article.php」の更新について触れていないためです。
下記の通り 4行目の「‘category_id’ => true,」を追加しましょう。
1 2 3 4 5 6 7 |
protected $_accessible = [ 'title' => true, 'body' => true, 'category_id' => true, 'created' => true, 'modified' => true ]; |
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でPHPExcelを使ってエクセルファイルを生成、出力する方法
CakePHP3でPHPExcelを利用してエクセルを編集、出力するサンプルソース+解説。PHPExcelのインストール方法の解説からファイル保存とダウンロードの方法なども解説。
-
CakePHP4のCakeDC/Usersの Usersへの接続、バリデーションのカスタマイズ方法解説
CakeDC謹製Usersプラグインの紹介。CakePHP4で使う場合のUsersのカスタマイズとして入力項目のバリデーションの変更を、プラグインのファイルは触らずオーバーライドにより実装する方法を解説する。
-
CakePHP3にOGPをfetch、asignを利用してテンプレートごとに指定する方法を解説
CakePHP3でOGPを設定する方法を解説。fetch、assignを使用しレイアウトファイルに編集した変数にテンプレートファイルから値を指定する。これを利用してOGPを編集する。
-
CakePHP3のOGPはHTMLヘルパーの$this->Html->meta()を使って設定
CakePHP3でOGPを設定する方法を解説。metaタグを編集するHTMLヘルパーを利用してOGPのタグを編集する。また、エレメントとして分割することでメンテナンス性も向上させる。
-
CakePHP3で保存前にバリデーション結果を取得する2つの方法
CakePHP3でデータベースに値を保存する前にバリデーションを行い、その結果によって処理を振り分ける方法について解説。「$topic->errors()」と「$topic->hasErrors()」の2つの方法がある。
-
CakePHP3でkeywords、DescriptionをHTMLヘルパーを使って設定する
CakePHP3のkeywordsとdescriptionを設定する方法の解説。CakePHP3にはmetaタグを編集するHTMLヘルパーが用意されているためそれを利用すればOK!ポイントはブロック化を有効にすること。
-
CakePHP3でファイルのアップロード処理を自作・解説付き・その1
CakePHP3でファイルをアップロードする処理を、php.netにある「エラーを起こさない」と説明がある処理を参考に作成。サンプルソースとその解説付きで、コピペでも動くし、カスタマイズも簡単!
-
CakePHP3でパンくずの指定は HTMLヘルパーを使って指定する方法を解説
CakePHP3でパンくずの指定方法の解説。2つのヘルパーがあるが簡単なHTMLヘルパーを使った方法を、実際の状況に合わせて3つのパターン(エレメント化、ブロック化)にして解説。
-
CakePHP3のcontroller内でテンプレート、レイアウトを変更する際の指定方法
CakePHP3でテンプレートファイルやレイアウトファイルをデフォルトのものから別のものに変更したい場合の指定方法を解説。
-
CakePHP 2.3で確認画面付きのお問い合わせフォームの作り方
CakePHPで確認画面付きのお問い合わせフォーム、メールフォームの作り方をサンプルを提示しながら解説。