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
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-  
            
- 
      CakePHP 2.3 ID以外のカラムでアソシエーション(連携)をさせる場合ID以外のカラムでアソシエーション(連携)させるための考え方とサンプルソースを用いての説明を行っています。 
-  
            
- 
      CakePHP2、CakePHP3、CakePHP4、CakePHP5のバージョンを調べる 2つの方法CakePHPのバージョンの調べ方2点を紹介。CakePHP3~CakePHP5は共通だが CakePHP2はフォルダ構成が異なるためコマンドのパスもオプションも異なる。 
-  
            
- 
      CakePHP5でヘルパーから他のヘルパーを読み込む方法・CakePHP4からの変更点CakePHP5のヘルパーで他のヘルパーを読み込む方法を解説。公式の日本語CookbookはCakePHP4のソースのままで間違っているため注意が必要。CakePHP4からの移行の際も同じ点に注意が必要。 
-  
            
- 
      CakePHP3チュートリアルで日付と時刻のDateTimeでエラーが出たときの対処方法CakePHP3のブックマークチュートリアルには記載ミスもあり、そのまま動かない個所もある。CakePHP3では namespaceを使うようになったので、classを呼び出すときに¥を追加する必要が! 
-  
            
- 
      CakePHP 2.3 主キー(ID)以外のキーで更新方法 updateAll主キー(ID)以外のカラムをキーとして更新する方法、updateAllの使い方をサンプルを用いて解説します。 
-  
            
- 
      CakePHP4系、CakePHP5系のexists()でカラムを指定して値の有無をチェックする方法解説CakePHP4、5で指定したカラムに特定の値に該当のレコードの有無をチェックするにはexists()を使う。単純にカラムを指定する方法から複数条件をand、orで探すこともできる。 
-  
            
- 
      CakePHP3でPHPExcelを使ってエクセルファイルを生成、出力する方法CakePHP3でPHPExcelを利用してエクセルを編集、出力するサンプルソース+解説。PHPExcelのインストール方法の解説からファイル保存とダウンロードの方法なども解説。 
-  
            
- 
      CakePHP 2.3 bakeの超初心者向けフォロー講座CakePHP 2.3 bakeの超初心者向けフォロー講座 
-  
            
- 
      CakePHPを学ぶ際にはオブジェクト指向を学ぼうCakePHPはオブジェクト指向で書かれていますので、CakePHPを学ぶにはオブジェクト指向も学びましょう。 
-  
            
- 
      CakePHP2の検索Plugin CakeDC/Searchで重複を省くgroup by(distinct)の実装方法CakePHP2の検索プラグイン「CakeDC/Search」で、重複レコードを省くgroup by、distinctを使う方法についての解説。設定する場所はpaginatorの条件とするので、find()関数と同じ。 
 
         
 

 
             
             
             
             
             
             
             
             
            