インストールしたPropelを実際に利用してみる(その5:Behaviors編[3])
こんにちは、株式会社ライオンハートの鵜飼です。
その1、その2、その3、その4に引き続き、Propelについてのメモその5です。
前回に引き続き、Behaviorsについてのメモを残していきます。公式ドキュメントはコチラですので、併せて参照ください。
なお、まだまだPropelについては面白そうな機能がありそうですが、一旦これでキリを付けておこうと思います。
NestedSet Behavior
利用するケースは少なそうですが、入れ子構造(ツリー構造)を持たせたデータを保存しておく場合の設定です。
<table name="section"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="title" type="varchar" required="true" primaryString="true" /> <behavior name="nested_set" /> </table>
データ挿入
実際に入れ子構造をもたせる場合の指定は、ちょっと複雑な記述となりますが下記のようになります。
<?php // $s1 の作成 $s1 = new Section(); $s1->setTitle('Home'); $s1->makeRoot(); // 大本(ルート)のデータを作成 $s1->save(); // $s2 の作成 $s2 = new Section(); $s2->setTitle('World'); $s2->insertAsFirstChildOf($s1); // $s1の下に挿入 $s2->save(); // $s3 の作成 $s3 = new Section(); $s3->setTitle('Europe'); $s3->insertAsFirstChildOf($s2); // $s2の下に挿入 $s3->save(); // $s4 の作成 $s4 = new Section(); $s4->setTitle('Business'); $s4->insertAsNextSiblingOf($s2); // $s2の後ろに挿入 $s4->save(); /* 構造は下記のような形になります $s1:Home | \ $s2:World $s4:Business | $s3:Europe */
入れ子構造に挿入する際に用意されているメソッドは、下記4つが準備されているようです。
- insertAsFirstChildOf
- 指定したデータの子の先頭に挿入
- insertAsLastChildOf
- 指定したデータの子の最後に挿入
- insertAsPrevSiblingOf
- 指定したデータの同列で、自分の前に挿入
- insertAsNextSiblingOf
- 指定したデータの同列で、自分の後に挿入
データ取得
取得する時もまたちょっとややこしいですが、こんな感じになります。
<?php $rootNode = SectionQuery::create()->findRoot(); // $s1 $worldNode = $rootNode->getFirstChild(); // $s2 $businessNode = $worldNode->getNextSibling(); // $s4 $firstLevelSections = $rootNode->getChildren(); // array($s2, $s4) $allSections = $rootNode->getDescendants(); // array($s2, $s3, $s4) // チェーンメソッドで取得することも可能です $europeNode = $rootNode->getLastChild() ->getPrevSibling() ->getFirstChild(); // $s3 $path = $europeNode->getAncestors(); // array($s1, $s2)
取得時のメソッドは、それぞれこんな役目のようです。
- findRoot
- 大本(ルート)のデータを取得
- getFirstChild
- 子のデータ群の内、先頭のデータを取得
- getLastChild
- 子のデータ群の内、最後のデータを取得
- getPrevSibling
- 同列のデータ群の内、自分の前のデータを取得
- getNextSibling
- 同列のデータ群の内、自分の後のデータを取得
- getAncestors
- 親のデータを再帰的に全て取得
- getDescendants
- 子のデータを再帰的に全て取得
データ判定
構造自体が複雑になるので、入れ子構造内のデータについての判定フラグが幾つか存在しています。
<?php echo $s2->isRoot(); // false : 大本(ルート)のデータか? echo $s2->isLeaf(); // false : 一番子(葉)のデータか? echo $s2->getLevel(); // 1 : 何階層目か? echo $s2->hasChildren(); // true : 子はあるか? echo $s2->countChildren(); // 1 : 子はいくつあるか? echo $s2->hasSiblings(); // true : 同列のデータ(兄弟)はあるか?
データ移動
登録されているデータを移動させることも可能です。
<?php /* データ挿入後の構造が存在している状態と仮定します $s1:Home | \ $s2:World $s4:Business | $s3:Europe */ // $s2を$s4の子に移動させる $s2->moveToFirstChildOf($s4); /* 移動すると構造は下記のようになります $s1:Home | $s4:Business | $s2:World | $s3:Europe */ // $s3を$s4の後に移動する $s3->moveToNextSiblingOf($s4); /* 移動すると構造は下記のようになります $s1:Home | \ $s4:Business $s3:Europe | $s2:World */
データ削除
データの削除についても通常の削除処理に加えて便利なメソッドが追加されます。
<?php /* データ移動後の構造が存在している状態と仮定します $s1:Home | \ $s4:Business $s3:Europe | $s2:World */ // $s4の子を全て削除 $s4->deleteDescendants(); /* 削除すると構造は下記のようになります $s1:Home | \ $s4:Business $s3:Europe */
データ検索
検索条件に入れ子構造を利用することができます。
<?php $children = SectionQuery::create() ->childrenOf($rootNode) ->orderByTitle() ->find();
また、前述の「データ取得」のメソッドに、条件を含めることも可能です。
<?php $orderQuery = SectionQuery::create()->orderByTitle(); $children = $rootNode->getChildren($orderQuery);
Sortable Behavior
Autoincrementに似たような機能ですが、自動的に採番を行い順位付けを行うことができる設定です。
<table name="task"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="title" type="varchar" required="true" primaryString="true" /> <behavior name="sortable" /> </table>
sortable_rank
というカラムが自動的に生成され、そこに採番が行われます。
<?php // $t1 を生成 $t1 = new Task(); $t1->setTitle('Wash the dishes'); $t1->save(); echo $t1->getRank(); // 1、 0は利用されません // $t2 を生成 $t2 = new Task(); $t2->setTitle('Do the laundry'); $t2->save(); echo $t2->getRank(); // 2 // $t3 を生成 $t3 = new Task(); $t3->setTitle('Rest a little'); $t3->save() echo $t3->getRank(); // 3
データ取得
sortable
を設定したテーブルのデータは順番に取得することができます。
<?php $firstTask = TaskQuery::create()->findOneByRank(1); // $t1 $secondTask = $firstTask->getNext(); // $t2 $lastTask = $secondTask->getNext(); // $t3 $secondTask = $lastTask->getPrevious(); // $t2 $allTasks = TaskQuery::create()->findList(); // => collection($t1, $t2, $t3) $allTasksInReverseOrder = TaskQuery::create()->orderByRank('desc')->find(); // => collection($t3, $t2, $t1)
データ判定
リスト内のデータについての判定用メソッドが用意されています。
<?php echo $t2->isFirst(); // false : 最初のデータか? echo $t2->isLast(); // false : 最後のデータか? echo $t2->getRank(); // 2 : 何番目のデータか?
データ移動
リストの順番は下記のようなメソッドを利用することで変更することが可能です。
<?php // 最初の順番 : $t1 - $t2 - $t3 // 下記はそれぞれ $t2 の位置を変更していきます // 一番上に移動する : $t2 - $t1 - $t3 $t2->moveToTop(); // 一番下に移動する : $t1 - $t3 - $t2 $t2->moveToBottom(); // 一つ上に移動する : $t1 - $t2 - $t3 $t2->moveUp(); // 指定の要素と入れ替える : $t2 - $t1 - $t3 $t2->swapWith($t1); // 指定位置に移動する : $t1 - $t3 - $t2 $t2->moveToRank(3); // 指定位置に移動する : $t1 - $t2 - $t3 $t2->moveToRank(2);
データ挿入
特定位置に挿入することも可能です。
<?php // 最初の順番 : $t1 - $t2 - $t3 $t4 = new Task(); $t4->setTitle('Clean windows'); $t4->insertAtRank(2); $t4->save(); // 挿入後の順番 : $t1 - $t4 - $t2 - $t3
データ削除
データを削除すると、自動的に採番を直してくれます。
<?php $t4->delete(); // 削除後の順番 : $t1 - $t2 - $t3
雑感
今回のBehaviorはちょっとややこしく出来ることが多い内容でしたが、実際に同じようなモノを作ろうとすると大変そうなので、便利な仕組みではあると感じますね。
その他、NestedSet
もSortable
も、単一の条件の処理しかサンプルを残していませんでしたが、どちらも複数のキーを持たせることが出来るようです。設定自体はややこしくなりますが、使いこなせれば面白そうです。
また、前置き書かせていただきましたが、Propelについてのまとめは一旦これで一区切りをさせていただいて、まだ何にしようか考えていませんが、新しい何かを探してみようと思います。
よろしくお願いいたします。