Tree Behavior & Tree Helper

posted 2009-07-01 | written by mon_sat

CakePHPのCore BehaviorであるTree Behavior は、簡単にツリー構造のデータを扱うことができる便利なビヘイビアです。
http://book.cakephp.org/ja/complete/91/Tree

さらに、bakeryで公開されている準公式HelperであるTree Helperと併せて利用すると、その簡単さに誰もが驚くことでしょう。

得意なデータはたとえば次のようなものがあります。

  • ディレクトリ
  • カテゴリ(親子形式のあるもの)
  • ユーザーグループ
  • その他

基本的に何らかのデータをhasManyするModelが適していると思われます。

例として、複数のArticleを持つCategory Model のテーブルをみてみましょう。

  • id    integer
  • name    string
  • article_count    integer    # counterCache用 ※任意
  • parent_id    integer    # Tree Behavior用
  • lft    integer    # Tree Behavior用
  • rght    integer    # Tree Behavior用
  • その他必要なフィールド

関連付けは以下の通りです。

# Category Model
var $hasMany = array('Article');
var $actsAs = array('Tree');

# Article Model
var $belongsTo = array('Category' => array(
    'counterCache' => true, # counterCache用 ※任意
));

# Categories Controller
var $helpers = array('Tree');

これだけで準備はできました。
なお、Tree Helper は、Bakeryよりコピーしてきてください。
他のappと共用しているpluginsディレクトリがあれば、そこに入れておくとよいでしょう。

基本的な使い方

その1:ArticlesControllerで、addやeditをするときのセレクトボックス用のデータを作成する

# ArticlesController
$categories = $this->Article->Category->generatetreelist(null ,null ,null ,' ');
# views/articles/edit.ctp
echo $form->input('category_id' ,aa('label',"カテゴリ"));

参照:http://book.cakephp.org/ja/view/517/generatetreelist

その2:カテゴリの一覧を取得する

# ArticlesController
$conditions = array(
    'Category.article_count >' => 0,
); // Articleが一件以上登録されているもののみ取得する場合
$this->data = $this->Category->find('all' ,compact('conditions') );

これは通常通りに取得可能です。

その3:カテゴリパス用のデータを取得する

# CategoriesController
$parents = $this->Category->getpath( $category_id );
$this->set('parents' ,$parents);
# view
foreach ($parents as $parent) {
    $html->addCrumbs(
        $parent['Category']['name'] ,
        '/categories/index/' . $parent['Category']['id']
    );
}
$html->getCrumbs('>' ,'TOP');

viewのコードはHelperにおいておき、layoutファイルから読みだすとスマートです。

参照:http://book.cakephp.org/ja/view/235/getpath

その4:カテゴリの順序を変える

カテゴリの並びを変更するにはふたつのやり方があります。
ひとつは、親カテゴリをつけかえることです。
たとえば以下のようなツリーを想定します。

  • CakePHP
    • View
      • element
      • ヘルパー
    • Controller
      • コンポーネント
    • Model
  • レイアウト
  • CSS
  • HTML

「レイアウト」のparent_idに「View」のidを指定すれば、レイアウトがViewの子供(のカテゴリ)になるので、ヘルパーの次に表示されるようになるでしょう。

# controller
$this->Category->id = 8; // 「レイアウト」の ID
$newParentId = $this->Category->field('id', array('name' => 'View'));
$this->Category->save(array('parent_id' => $newParentId));

では、ヘルパーをelementより先に表示したい場合はどうするのかというのが二つ目の方法です。
「同じ階層の中で順序を変更するとき」はmoveUpおよびmoveDownメソッドを使用します。

マニュアルでは階層を上下させることができるような記述がありますが、あくまで同じ階層のなかで順位を変更するメソッドです。
上記の例でelementをmoveUpしてもすでに同階層の先頭に位置しているため、moveUpは失敗します。

# controller
// ひとつ下に順位を下げる
$this->Category->moveDown($this->Category->id, 1);
# controller
// ひとつ上に順位を上げる
$this->Category->moveUp($this->Category->id, 1);

参照:http://book.cakephp.org/ja/view/229/Advanced-Usage
マニュアルに詳しい使用例が載っているので参考にしてください。

その5:初期データを設定する

Tree Behaviorを使うときに真っ先に苦労するのが、初期データの登録です。

もともと何らかのデータがある場合、それをインポートするために、単純にphpMyAdminなどでinsertしようとしても、lftやrghtの値を自分でつけるわけにはいかずに右往左往してしまいます。

で、そんなときは、lft・rhtをnullのままにしておき、id,name,parent_idのみを(何らかの方法で)インポートしてください。
あとは、以下のメソッドを実行するだけで、すべてのlft,rghtが割り振られます。

# controller
$this->Category->reorder();

admin_reorderというactionにしておくとよいかもしれません。

また、recoverというメソッドもあり、何らかの不用意な操作でTree構造が壊れたときは、これで修復が試みられるそうです。

私は、まだ実際に使ったことはありません。

その6:データの削除

addやeditは通常通りsave()で行いますが、delete()してしまうと、Tree構造が壊れてしまいます。
delete()の代わりにremoveFromTree()が用意されていますので、そちらを利用しましょう。

Tips

Modelであらかじめ $order = array('lft ASC'); としておくと、常に順序よくデータを取得できます。


プロフィール

@mon_sat

CakePHPをよく利用しています。

理解の浅かった半年前と、何も知らなかった一年前の自分への教科書として書いています。
当たり前のことも平易に。

RSS2.0

カテゴリ別エントリ一覧

タグ別エントリ一覧

アーカイブ