はじめに
以下の解説は この記事 ((c) James Simpson) にもとづいています。
入れ子集合モデルは、リレーショナルデータベースにおける入れ子集合 (ツリーや階層構造と言ってもいいです)を実現する方法の 1 つです。
この用語は Joe Celko によって紹介されたものだと言われていますが、同じテクニックが特に名前を付けられることもないまま、あるいは別の名前によって
語られることもあります。(出典: ウィキペディア(英語))
よくあるツリー構造の例は、組織の構造図です。この例を使って、入れ子集合がどのような働きをするかを説明していきます。
上図は各ノードがそれぞれの子孫全てについて把握していて、その逆もまた真であることを示しています。たとえば、この図からは
Ben と Angela が Bill の「子」であることが容易に見てとれます。 また、 Tim と James が Ben の「子」であることも分かります。
これは詰まるところ、階層構造の中の各要素間のつながりを示しているものです。
この構造を表現するためには、各ノードにおいて 2 つの値を余分に保存する必要があり、これによってツリー構造内の
ノード間のつながりを定めます。図の中では青と赤で示しています。これら左側の値 (赤の値) と
右側の値 (青の値) を、ポインターと呼びます。
ポインターをテーブル上に保存するだけで、入れ子集合による階層構造を完全に表現することができます。
数学の好きな人なら、ツリー構造は別の形で図示することもできるということを知っている筈です。
一見するとよく分からないかもしれませんが、というかおそらく余計に読み難く感じるとは思うのですが、このように表せば何故これが
入れ子集合と呼ばれるのか、また何故この形で階層的なデータを平面的なテーブル上へ格納するのが効率的なのかは、一目瞭然となります。
親子間の関連性はパッと見で分かり (全ての子が親の中に含まれています) 、またそれぞれのノードは自身の親や、そして自身の全ての
子孫について把握しています。この形で構造を表すことによって、全てのポインタが美しく並んでいることがはっきり分かるようになります。このことは
ツリー構造へアクセスする必要がある際に、非常に便利なものとなります。
単一のテーブルで複数のツリー構造を保存する
単一のテーブルで複数のツリー構造を保存することもできます。これを可能とするために、ツリーの ID を
テーブルのカラムに追加することもできます。ツリー ID があれば、ノードがどのツリーのものかを識別することができます。
ツリー ID には整数値でも文字列でも使えるのですが、整数値であれば、ツリーを新しく作る際に自動生成させることもできます。
整数値ならツリー ID を 1 対他のリレーションでの外部キーとして使うことも容易となり、 DB 内でのインデックスのルックアップも
より高速になります。なるべく文字列をツリー ID に使うことはしない方がよいです。
テーブル構造
テーブルには必要とするだけ幾つでもカラムを追加できますが、ちゃんと動かすためには、入れ子集合モデルには最低限必要な
カラムが幾つかあります。
- 左ノードポインター、デフォルトのカラム名は 'left_id'
- 右ノードポインター、デフォルトのカラム名は 'right_id'
- ツリー ID 、テーブル内に複数のツリーを含む場合のみ必須 (デフォルトでは必要ありません)
実際のカラム名は、モデル内で設定することができます。厳密に言うと必要というわけではないのですが、
幾つかのメソッドは "title" や "name" というフィールドを使います。これらのカラム名もまたモデル内で設定することができます。
もしこれらのフィールドを要求するメソッドを使う際、フィールドが定義されていなければ、例外が投げられます。
以下は入れ子集合テーブルの最小構成です。
CREATE TABLE `tree` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`left_id` int(11) unsigned NOT NULL,
`right_id` int(11) unsigned NOT NULL,
`tree_id` int(11) unsigned NOT NULL,
`name` varchar(50),
PRIMARY KEY (`id`),
KEY `left_id` (`left_id`), // 任意、一部の参照が高速化されるはず
KEY `right_id` (`right_id`), // 任意、一部の参照が高速化されるはず
)
設定
Nestedset モデルでは追加のプロパティとして $_tree が加わり、これによってそのモデルの設定をすることができます。
- left_field はテーブルのカラム名を定義するもので、ツリーの左ポインターを格納するのに使われます。デフォルトのカラム名は "left_id" です。
- right_field はテーブルのカラム名を定義するもので、ツリーの右ポインターを格納するのに使われます。デフォルトのカラム名は "right_id" です。
- tree_field はテーブルのカラム名を定義するもので、ツリーの ID 、つまり複数のツリー構造を単一のテーブルへ格納するときに
各ツリーを一意に識別する値を格納するのに使われます。ツリー ID に数値を使う場合は、新たなツリーのルートノードを作る際に、モデルへ自動的にこの値を
割り当てさせることができます。数値でない場合は、 save() を呼ぶ前に自分でその値を与えてやる必要があります。デフォルトの値はありません。
- title_field はテーブルのカラム名を定義するもので、ツリーのノードのタイトルや名前を格納するのに使われます。これは任意のもので、
なくても入れ子集合モデルは十分機能するのですが、このモデルの幾つかのメソッドにおいては要求されます。どこで適用されるのかについては、当該メソッドについてのドキュメントで
言及しています。デフォルトの値はありません。
次の節では基本的な Nestedset モデルがどのようなものなのか、またどのように設定するのかを示します。
モデルの定義
入れ子集合モデルの利用は簡単で、 \Orm\Model のかわりに \Orm\Model_Nestedset を継承するだけです。
これによってあなたのモデルは入れ子集合モデルとなり、ツリーやノードを扱うための沢山のメソッドが追加され、また
delete() や save() メソッドのデフォルトの挙動が変わることで、ツリー構造がきちんと一貫性を保てるようになります。
<?php
class Model_Tree extends \Orm\Model_Nestedset
{
/**
* @var string このモデルで使用されるテーブルの名前
*/
protected static $_table_name = 'tree';
/**
* @var array オブジェクトがもつプロパティの配列
*/
protected static $_properties = array(
'id',
'left_id',
'right_id',
'tree_id',
'name',
);
/**
* @var array ツリーの設定値をもつ配列
*/
protected static $_tree = array(
'left_field' => 'left_id', // ツリーノードの左ポインター用のフィールド名
'right_field' => 'right_id', // ツリーノードの右ポインター用のフィールド名
'tree_field' => 'tree_id', // ツリーノードのツリーid用のフィールド名
'title_field' => 'name', // ツリーノードのタイトル用フィールド名
);
}
?>
見ての通り、まったく標準的な ORM モデルの設定です。 唯一追加されたクラスプロパティが $_tree で、これは
ツリーの設定を定義するものとなります。このモデルはこの設定から通常の ORM モデルと同様に構築され、いかなるリレーションや
他のプロパティも思うままに使うことができます。
モデルの使用法
最初に挙げた組織構成図の例と先ほど定義したモデルを使って、そのツリー構造を作ることからやってみましょう。
<?php
// Bill 社長、つまりこのツリーのルート
$bill = Model_Tree::forge(array('name' => 'Bill'));
// 新しくノードを保存する際、自動的にツリーのルートノードとなる
$bill->save();
// 社長に部下を与える
$angela = Model_Tree::forge(array('name' => 'Angela'));
$ben = Model_Tree::forge(array('name' => 'Ben'));
// 彼らはツリー上で Bill の子となる
$angela->child($bill)->save();
$ben->child($bill)->save();
// その他カスタマーサービスを追加
$henry = Model_Tree::forge(array('name' => 'Henry'));
$henry->child($angela)->save();
$nicola = Model_Tree::forge(array('name' => 'Nicola'));
$nicola->child($angela)->save();
// そして開発チームを追加
$kerry = Model_Tree::forge(array('name' => 'Kerry'));
$kerry->child($ben)->save();
// Kerry の兄弟は Ben の子でもある
$james = Model_Tree::forge(array('name' => 'James'));
$james->sibling($kerry)->save();
// チームの若いのを Kerry のところに割り当てる
$tim = Model_Tree::forge(array('name' => 'Tim'));
$tim->child($kerry)->save();
?>
見ての通り、ツリー構造の作成とノードの追加はいい感じに簡単なものです。 Nestedset モデルと他の ORM モデルとの間にある
主な違いは、モデルの他のオブジェクトに対して影響するメソッドを持つかどうか、という点にあります。単に問い合わせをそのモデルに対して
行ったり、あるオブジェクトを保存するというのではなく、ツリー構造内の別のオブジェクトに対してそれらのことを行う、ということです。これは
従来通りのメソッドが使えないという意味ではなく、Model::find() や Model::query() なども依然、他の ORM モデルと同様に使うことができます。
それでは次はこのツリー構造から情報を得たり、ツリー構造を操作したりするのに使えるメソッドの説明へ
進みましょう。
挙動の変更点
ORM モデルにおいてプライマリーキーは、オブジェクトが一度構築されたあとは読み込み専用のデータとなるのですが、これに加えて入れ子集合モデルでは
ノードの左右ポインタを変更することができません。定義されている場合はツリー ID についても同様です。変更しようとした場合、
代入や set() の呼び出し、また unset() のいずれも InvalidArgumentException となります。
save($cascade = null, $use_transaction = false)
save メソッドは通常モデルの実装とまったく同じように機能します。
しかし、このメソッドを新しいツリーノードのオブジェクトにおいて使う場合は、ツリー構造の一貫性を保つための追加機能が
付与されます。あるオブジェクトにおいて呼び出した際に、それがあとで述べるコレクションメソッドのいずれかを使ってからのことであれば、
新規作成されたオブジェクトはそのコレクションメソッドへ渡されていたオブジェクトと紐付いたツリーに挿入されます。
事前にコレクションメソッドを使っていなかった場合、その save は新たなルートノードを挿入しようとしているものと見なされます。モデルが
複数ツリーモデルの場合、新たなルートが次に使えるツリー ID とともに作成されます (これはツリー ID が数値型の場合の話で、そうでない場合は
自分でツリー ID をノードのオブジェクトに割り当てる必要があります) 。
ルートノードが既に存在しているため作れなかった場合、 OutOfBoundsException が投げられます。もし
ツリー ID が数値型でなく、また手動で ID を指定してもいない場合、その結果は 予測不可能 です!
delete($cascade = null, $use_transaction = false)
delete メソッドは通常モデルの実装とまったく同じように機能します。
削除するノードがリーフノードではなかった場合 (つまり子を持つ場合) 、そのノードの全ての子孫はツリー上で 1 階層上に移動し、
削除するノードの親の子孫となります。ツリーの全体 (または部分木) を削除したい場合は、かわりに
delete_tree() メソッドを使います。
delete() メソッドを 1 つより多くの子を持つルートノードに対して使うことはできません。
もしそれができると、整合性のないツリー構造を作る事になります。複数のノードを一番上の階層に持つことはできないからです。使おうとした場合は
DomainException が投げられます。
コレクションメソッド
コレクションメソッドとは get() か save() 、いずれかの操作を入れ子集合ツリーの一部分に対して
行えるようにするメソッド群のことです。幾つかのメソッドは両方で利用でき、また幾つかは片方にのみ関係しています。明確性のため、これらを別々に
説明します。コレクションメソッドをサポートされていない操作について使う場合、 OutOfBoundsException が
投げられます。
ノードを得るためのコレクションメソッド
通常の ORM モデルと同様に、 get() メソッドをオブジェクトの配列を得るために使うことができ、また
get_one() メソッドを単一のオブジェクトを得るために使えます。 あるコレクションメソッドが単一の結果を得るよう想定されたものの場合、
たとえば root() のような場合、 get() はその単一のオブジェクトを持つ配列を返します。もし
あるコレクションメソッドが複数のオブジェクトを返すものの場合、たとえば children() の場合、 get_one() は
その結果セットから最初のオブジェクトを返します。どの 1 つが返されるかは不定です。
get() と get_one() は結果が見つからなかった場合、 null を返します。
root()
root メソッドで、呼び出したオブジェクトの属するツリー構造のルート (根) を取得できます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $henry が属するツリーのルートを取得
$root = $henry->root()->get_one();
// 'true' を echo
echo $root == $bill ? 'true' : 'false';
// 特定のツリーのルートを取得
$root = Model_Tree::forge()->set_tree_id($mytreeid)->root()->get_one();
|
roots()
roots メソッドで、全てのルートノードを取得できます。モデルが複数ツリーのモデルではない場合、
1つのルートノードだけが返されます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// ツリー内のすべてのルートを取得
$roots = $henry->roots()->get();
// 'true' を echo (このツリー内には 1 つのルートだけがある)
echo reset($roots) == $bill ? 'true' : 'false';
// 既存のオブジェクトを起点とせずにすべてのルートを取得
$roots = Model_Tree::forge()->roots()->get();
|
parent()
parent メソッドで、呼び出したオブジェクトの親ノードを取得することができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $nicola の親を取得
$parent = $nicola->parent()->get_one();
// 'true' を echo
echo $parent == $angela ? 'true' : 'false';
// null が返る。ノードのツリーコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->parent()->get_one();
|
children()
children メソッドで、呼び出したオブジェクトの全ての子ノードを取得することができます。
注意してほしいのは、このメソッドは子のみを返し、孫は返さないということです。部分木の全てを取得したい場合は、
descendants メソッドをかわりに使って下さい。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $angela の子を取得
$children = $angela->children()->get();
// 'true' を echo
echo $children == array($henry->id => $henry, $nicola->id => $nicola) ? 'true' : 'false';
// null が 返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->children()->get();
|
ancestors()
ancestors メソッドで、呼び出したオブジェクトの全ての先祖ノードを取得することができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $henry の先祖を取得
$ancestors = $henry->children()->get();
// 'true' を echo
echo $ancestors == array($bill->id => $bill, $angela->id => $angela) ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->ancestors()->get();
|
先祖ノードはツリー構造内での位置の順で返され、ルートノードが最初のものとなります。
descendants()
descendants メソッドで、呼び出したオブジェクトの全ての子孫を得ることができます。
これによって部分木の全てを取得できます。 結果には開始ノード自身は含まれ「ません」。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $ben の子孫を取得
$descendants = $ben->descendants()->get();
// 'true' を echo
echo $descendants == array($kerry->id => $kerry, $tim->id => $tim, $james->id => $james) ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->descendants()->get();
|
子孫ノードはツリー構造内での位置の順で返されます。
leaf_descendants()
leaf_descendants メソッドで、呼び出したオブジェクトの末端の子孫ノード (リーフノード) を取得することができます。
descendants メソッドとは違い、このメソッドは子を持たないノードのみを返します
(ツリー構造の葉) 。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $ben の末端の子孫を取得
$descendants = $ben->leaf_descendants()->get();
// 'true' を echo。$kerry は子のオブジェクトを持つため、ここでは返されないことに注意
echo $descendants == array($tim->id => $tim, $james->id => $james) ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->leaf_descendants()->get();
|
末端の子孫ノードはツリー構造内での位置の順で返されます。
siblings()
siblings メソッドで、呼び出したオブジェクトの全ての兄弟ノードを取得することができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $kerry の兄弟を取得
$siblings = $kerry->siblings()->get();
// 'true' を echo
echo $siblings == array($kerry->id => $kerry, $james->id => $james) ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->siblings()->get();
|
兄弟ノードはツリー構造内での位置の順で返されます。注意してほしいのは、結果セットには現在のノードが含まれるということです!
child()
child メソッドは、 last_child メソッドの別名です。
first_child()
first_child メソッドで、呼び出したオブジェクトの最初の子ノードを得ることができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $bill の最初の子を取得
$child = $bill->first_child()->get_one();
// 'true' を echo
echo $child == $angela ? 'true' : 'false';
// null が 返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->first_child()->get_one();
|
last_child()
last_child メソッドで、呼び出したオブジェクトの最後の子ノードを得ることができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $bill の最後の子を取得
$child = $bill->last_child()->get_one();
// 'true' を echo
echo $child == $ben ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->last_child()->get_one();
|
sibling()
sibling メソッドは next_sibling メソッドの別名です。
previous_sibling()
previous_sibling メソッドで、呼び出したオブジェクトの 1 つ前の兄弟ノードを得ることができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $nicola の前の兄弟を取得
$sibling = $nicola->previous_silbing()->get_one();
// 'true' を echo
echo $sibling == $henry ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->previous_sibling()->get_one();
// こちらも null が返る。$henry には前の兄弟がない
$sibling = $henry->previous_silbing()->get_one();
|
next_sibling()
next_sibling メソッドで、呼び出したオブジェクトの次の兄弟ノードを得ることができます。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// $henry の次の兄弟を取得
$sibling = $henry->next_silbing()->get_one();
// 'true' を echo
echo $sibling == $nicola ? 'true' : 'false';
// null が返る。現在のノードのコンテキストがなければうまくいかない
$parent = Model_Tree::forge()->previous_sibling()->get_one();
// こちらも null が返る。$nicola には次の兄弟がない
$sibling = $nicola->next_silbing()->get_one();
|
path($addroot = true)
path メソッドは現在のノードまでのパスを文字列として返し、この際に使われるカラムは
モデルのツリー設定の "title_field" で指定されたものとなります。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$addpath |
true |
偽の場合、ルートノードは結果から除外されます。 |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例外 |
OutOfBoundsException が、モデルで "title_field" 列を定義していない場合に投げられます。 |
例 |
// Tim までのパスを取得。"Bill/Ben/Kerry/Tim" を返す
$path = $tim->path()->get();
// Nicola までのパスを取得。"Bill/Angela/Nicola" を返す
$path = $nicola->path()->get();
// Kerry までのパスを取得。ボスについては除外。"Ben/Kerry" を返す
$path = $kerry->path(false)->get();
|
ノードを保存するためのコレクションメソッド
通常の ORM モデルと同様に、 save() はオブジェクトをテーブル内の新たなレコードとして挿入するか、
またはオブジェクトがデータベースから取得されたものの場合は既存のレコードを更新します。
しかし、通常の ORM モデルの場合とは異なり、入れ子集合ツリー内のものの場合は実際にツリー内のどこにオブジェクトが保存されているかが
非常に密接に関係しています。保存の前にコレクションメソッドが指定されていない場合、既存のオブジェクトの保存については単にそのまま行われ、通常の
ORM モデルの挙動と全く同じです。コレクションメソッドを指定した場合、そのオブジェクトは保存時にツリー構造内で再配置されます。
新規のオブジェクトを保存する場合は、コレクションメソッドを指定しなければ新たなルートノードが作られます。また指定した場合、それによって
ツリー構造のどこに新たなノードが挿入されるかが決まります。
child($to = null)
child メソッドは last_child メソッドの別名です。
first_child($to = null)
first_child メソッドは、呼び出したオブジェクトを渡されたオブジェクトの (またはプライマリーキーの) ノードの
最初の子として保存するメソッドです。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
呼び出しに使われたオブジェクトの親となるノードのオブジェクトかプライマリキー |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// Bill に部下を与える。ツリー上では Angela の前
$john = Model_Tree::forge(array('name' => 'John'))->first_child($bill)->save();
// Kerry を Bill の部下に昇進させる
$kerry->first_child($bill)->save();
|
last_child($to = null)
last_child メソッドは、呼び出したオブジェクトを渡されたオブジェクトの (またはプライマリーキーの) ノードの
最後の子として保存するメソッドです。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
呼び出しに使われたオブジェクトの親となるノードのオブジェクトかプライマリキー |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// Bill に新たな部下を与える。ツリー上では Ben の後
$john = Model_Tree::forge(array('name' => 'John'))->last_child($bill)->save();
// Kerry を Bill の部下に昇進させる
$kerry->last_child($bill)->save();
|
階層という観点では、first_child も last_child も同じことをします。ただ
ツリー構造内での正確な位置、挿入やノードの再配置をした後の兄弟との関係のみが異なります。
sibling($to = null)
sibling メソッドは next_sibling メソッドの別名です。
previous_sibling($to = null)
previous_sibling メソッドは、呼び出したオブジェクトを渡されたオブジェクトの (またはプライマリーキーの) ノードの
前の兄弟 (渡されたオブジェクトの直前、ツリー構造内で同じ階層のもの) として保存するメソッドです。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
呼び出しに使われたオブジェクトの次の兄弟オブジェクトとなるノードのオブジェクトかプライマリキー |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// Bill に新たな部下を与える。ツリー上では Angela の前
$john = Model_Tree::forge(array('name' => 'John'))->previous_sibling($angela)->save();
// James を Ben の部下、Tim の隣へ降格させる
$james->previous_sibling($tim)->save();
|
next_sibling($to = null)
next_sibling メソッドは、呼び出したオブジェクトを渡されたオブジェクトの (またはプライマリーキーの) ノードの
次の兄弟 (渡されたオブジェクトの直後、ツリー構造内で同じ階層のもの) として保存するメソッドです。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
呼び出しに使われたオブジェクトの前の兄弟オブジェクトとなるノードのオブジェクトかプライマリキー |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// Bill に新たな部下を与える。ツリー上では Angela の後
$john = Model_Tree::forge(array('name' => 'John'))->next_sibling($angela)->save();
// James を Ben の部下、Tim の隣へ降格させる
$james->next_sibling($tim)->save();
|
検査メソッド
検査メソッドはオブジェクトのツリー構造内での位置に関する特定の状態を問い合わせるものです。
is_root()
is_root メソッドは呼び出したオブジェクトがルートノードかどうか示します。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Bool。オブジェクトがルートノードであれば真、そうでなければ偽 |
例 |
// Bill がルートノードか? 'true' を echo
echo $bill->is_root() ? 'true' : 'false';
// Tim がルートノードか? 'false' を echo
echo $tim->is_root() ? 'true' : 'false';
|
is_leaf()
is_leaf メソッドは呼び出したオブジェクトが末端であるか、つまり子ノードを持たないかどうかを示します。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Bool。オブジェクトが末端ノードなら真、そうでなければ偽 |
例 |
// Tim が末端ノードか? 'true' を echo
echo $tim->is_leaf() ? 'true' : 'false';
// Angela が末端ノードか? 'false' を echo
echo $angela->is_leaf() ? 'true' : 'false';
|
is_child()
is_child メソッドは呼び出したオブジェクトが子ノードであるか、つまり
親ノードを持つかどうかを示します。これは is_root() の逆のものです。全ての非ルートノードは親を持つからです。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Bool。オブジェクトが子ノードなら真、そうでなければ偽 |
例 |
// Henry がルートノードか? 'true' を echo
echo $henry->is_child() ? 'true' : 'false';
// Bill がルートノードか? 'false' を echo
echo $bill->is_child() ? 'true' : 'false';
|
is_child_of(Model_Nestedset $to = null)
is_child_of メソッドで、呼び出したオブジェクトが渡されたオブジェクトのノードの子ノード (直接の子孫) であるかを
確認することができます。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
検査したいノード |
|
戻り値 |
Bool。オブジェクトが与えられたノードの子ノードなら真、そうでなければ偽 |
例 |
// Ben が Bill の子ノードか? 'true' を echo
echo $ben->is_child_of($bill) ? 'true' : 'false';
// James が Bill の子ノードか? 孫なので 'false' を echo
echo $james->is_child_of($bill) ? 'true' : 'false';
// Nicola が Ben の子ノードか? ツリーの違う部分なので 'false' を echo
echo $nicola->is_child_of($ben) ? 'true' : 'false';
|
is_descendant_of(Model_Nestedset $to = null)
is_descendant_of メソッドで、呼び出したオブジェクトが渡されたオブジェクトのノードの子孫ノードであるかを
確認することができます。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
検査したいノード |
|
戻り値 |
Bool。オブジェクトが与えられたノードの子孫なら真、そうでなければ偽 |
例 |
// Ben が Bill の子孫ノードか? 'true' を echo
echo $ben->is_descendant_of($bill) ? 'true' : 'false';
// James が Bill の子孫ノードか? 孫なので 'true' を echo
echo $james->is_descendant_of($bill) ? 'true' : 'false';
// Nicola が Ben の子孫ノードか? ツリーの別の部分なので 'false' を echo
echo $nicola->is_descendant_of($ben) ? 'true' : 'false';
|
is_parent_of(Model_Nestedset $to = null)
is_parent_of メソッドで、呼び出したオブジェクトが渡されたオブジェクトのノードの親ノードであるかを
確認することができます。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
検査したいノード |
|
戻り値 |
Bool。オブジェクトが渡されたノードの親ノードなら真、そうでなければ偽 |
例 |
// Bill が Ben の親か? 'true' を echo
echo $bill->is_parent_of($ben) ? 'true' : 'false';
// Bill が James の親か? 祖父なので 'false' を echo
echo $bill->is_parent_of($james) ? 'true' : 'false';
|
is_ancestor_of(Model_Nestedset $to = null)
is_ancestor_of メソッドで、呼び出したオブジェクトが渡されたオブジェクトのノードの先祖ノードであるかを
確認することができます。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$to |
null |
検査したいノード |
|
戻り値 |
Bool。オブジェクトが与えられたノードの祖先なら真、そうでなければ偽 |
例 |
// Bill が Ben の祖先か? 'true' を echo
echo $bill->is_ancestor_of($ben) ? 'true' : 'false';
// Bill が James の祖先か? 祖父なので 'true' を echo
echo $bill->is_ancestor_of($james) ? 'true' : 'false';
|
is_same_model_as(Model_Nestedset $to = null)
このメソッドは主として内部的に、 2 つの異なる Nestedset モデルをまたがる操作を避けるために使われます。
is_same_tree_as(Model_Nestedset $to = null)
このメソッドは主として内部的に、 2 つの異なる Nestedset モデルをまたがる操作を避けるために使われます。
両方のオブジェクトが同じモデルのインスタンスかをチェックするだけではなく、 両方のインスタンスが
複数ツリーモデルで同じツリーに属するかもチェックします。
has_parent()
has_parent メソッドは is_child メソッドの別名です。
has_children()
has_children メソッドは is_leaf メソッドの逆です。
あるオブジェクトが末端でないなら、それは子ノードを持っているということです。
has_previous_sibling()
has_previous_sibling メソッドは呼び出したオブジェクトが前の兄弟を持つかどうかを示します。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Bool。オブジェクトが前の兄弟を持つなら真、そうでなければ偽 |
例 |
// James が前の兄弟を持つか? 'true' を echo
echo $james->has_previous_sibling() ? 'true' : 'false';
// Tim が前の兄弟を持つか? 'false' を echo
echo $tim->has_previous_sibling() ? 'true' : 'false';
|
has_next_sibling()
has_next_sibling メソッドは呼び出したオブジェクトが次の兄弟を持つかどうかを示します。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Bool。オブジェクトが次の兄弟を持つなら真、そうでなければ偽 |
例 |
// Henry が次の兄弟を持つか? 'true' を echo
echo $henry->has_next_sibling() ? 'true' : 'false';
// Ben が次の兄弟を持つか? 'false' を echo
echo $ben->has_next_sibling() ? 'true' : 'false';
|
その他のメソッド
delete_tree($cascade = null, $use_transaction = false)
delete_tree は delete() メソッドと同じように動作しますが、違うのは
このメソッドが全ての子についても削除「する」という点です。
delete_tree() メソッドをルートノードに対して使った場合、そのツリー全体が削除されます!
tree_config($name = null)
このメソッドはモデルの 1 つ、または全てのツリー設定値を取得するのに使います。主として内部的に、
ツリーの一部分や、複数ツリーモデルでの特定のツリーを操作するクエリを構築するのに使われます。
静的 |
はい |
パラメータ |
パラメータ |
デフォルト |
説明 |
$name |
null |
取得する設定値の項目名 |
|
戻り値 |
設定項目の値か値の配列、 要求された値が存在しなかった場合は null |
例 |
// ツリー ID のカラム名を取得
$tree_id = Model_Tree::tree_config('tree_id');
|
get_tree_id()
このメソッドは複数ツリー環境においてツリー ID を取得するのに使います。もし有効な操作対象ノードがある場合、
そのノードのツリー ID が返されます。そうでない場合、 set_tree_id() で設定された値が返されます。
ツリー ID がまだ設定されていない場合は、例外が投げられます。このメソッドは主として内部的に使われ、クエリが複数ツリー環境でも
確実に現在のツリーのコンテキストにおいて実行されるようにします。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
mixed。現在のツリーのツリー ID、またはデフォルトのツリー ID のセット |
例 |
// tree_id を取得する(1 を返す)
$current_tree = $bill->get_tree_id();
|
set_tree_id($tree = null)
このメソッドは複数ツリー環境でツリー ID を設定するのに使います。主な使い道としては、
get() を get_one() 実行したいものの、既存の操作対象ノードがない場合があります。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$tree |
null |
選択するツリーのツリー ID。引数がなしか null の場合、ツリー ID の設定がリセットされる |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// あるツリーのルートを既存のノードを起点とせずに取得する
$root = Model_Tree::forge()->set_tree_id($mytreeid)->get_one();
|
build_query()
build_query メソッドは ORM のクエリオブジェクトを返します。 Model_Nestedset::query() と同様のものですが、
追加的な where 句が複数ツリーモデルの場合に付与され、これはクエリの実行が現在のツリーについて行われることを
保証するためのものです。これにはツリー ID が利用可能なことが必要なので、新規のオブジェクトにおいて呼び出す場合、set_tree_id を
有効な ID が定義されているのを保証するのに使ってください。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
ORM Query オブジェクト |
例 |
// 現在のツリー内で enabled がセットされたすべてのノードを取得する
$enabled = Model_Tree::forge()
->set_tree_id($mytreeid)
->build_query()
->where('enabled', '=', 1)
->get();
// 既存のオブジェクトを使う場合。$bill のオブジェクトのツリー ID が使われる
$enabled = $bill
->build_query()
->where('enabled', '=', 1)
->get();
|
get_query()
get_query メソッドは ORM のクエリオブジェクトを返しますが、 build_query() とは違い、
コレクションメソッドが事前に使われていることを必要とします。このメソッドによりモデルの標準的なメソッドを最初のノードの絞込に使い、
返されたクエリオブジェクトによってクエリの細かい調整を行うことができるようになります。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
ORM Query オブジェクト |
例 |
// Bill の enabled がセットされたすべての子ノードを取得する
$enabled = $bill
->children()
->get_query()
->where('enabled', '=', 1)
->get();
|
count_children()
count_children メソッドは呼び出したオブジェクトの子 (直接の子孫) の数を返します。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Integer |
例 |
// Bill の子の数を取得。2 を echo
echo $bill->count_children();
// Kerry の子の数を取得。1 を echo
echo $kerry->count_children();
// Tim の子の数を取得。0 を echo
echo $tim->count_children();
// これも 0 を echo。新たなオブジェクトは子を持たない
echo Model_Tree::forge()->count_children();
|
count_descendants()
count_descendants メソッドは呼び出したオブジェクトの子孫の数を返します。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Integer |
例 |
// Bill の子孫の数を取得。7 を echo
echo $bill->count_descendants();
// Ben の子の数を取得。3 を echo
echo $ben->count_descendants();
// 0 を echo。新たなオブジェクトは子孫を持たない
echo Model_Tree::forge()->count_descendants();
|
depth()
depth メソッドは現在のノードがツリー内のどの深さ、階層にあるかを返し、この際
ルートノードの階層は 0 となります。
静的 |
いいえ |
パラメータ |
なし。
|
戻り値 |
Mixed。整数か、現在のオブジェクトが有効でない場合は偽 |
例 |
// 組織内での Bill の階級を取得
echo $bill->depth(); // 0 を echo。彼が親分だ!
// 組織内での Angela の階級を取得
echo $angela->depth(); // 1 を echo。彼女は N+1 のため
// 組織内での Tim の階級を取得
echo $tim->depth(); // 3 を echo。いちばんの下っ端だ……
// 偽を返す。新たなノードの階層を取得することはできない
$result = Model_Tree::forge()->depth();
|
dump_tree($as_object = false, $children = 'children', $path = 'path', $pathuri = null)
dump_tree メソッドはツリー全体を返し、これは現在のオブジェクトをルートノードとして
多次元配列として返します。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$as_object |
false |
真なら、多次元配列ではなくオブジェクトの階層的なツリー構造を返します。 |
$children |
'children' |
子ノードのデータはこの名前のプロパティ、もしくは配列のキー名で付与されます。 |
$path |
'path' |
この名前でルートからそのノードへのパスを持つプロパティが作られます。
これには 'title_field' が設定されている必要があります!
|
$pathuri |
null |
この引数は入力として既存のテーブルのカラムを必要とします。定義されれば、指定されたカラム名を使い、
ルートを指すパス定義を含む "path_" を頭に付けたこの引数名のプロパティを作成します。
これは主にモデルが URL や URI のカラムを含む場合に用いられ、
パンくずのパス ('path' の中に) と URI のパスの両方を保有できます。
|
|
戻り値 |
Mixed。多次元配列か、または現在のオブジェクトに子が追加されたもの |
例 |
// 多次元配列を返す。$tree['children'] が Bill の子を持つ
$tree = $bill->dump_tree();
// 'children' プロパティとして Bill の子を持つ 'bill' オブジェクトを返す
$tree = $bill->dump_tree(true);
// $tree を使うと、array($angela, $ben) を返す
$children = $tree->children;
// そしてこちらは array($henry, $nicola)、つまり $angela の子を返す
$grandchildren = reset($children)->children;
// ORM オブジェクトは常に参照として扱われる!
$bill->dump_tree(true);
// ので、こっちでもいける
$children = $bill->children;
|
全ての ORM 配列がそうであるように、テーブルのプライマリーキーが配列の添字として使われます。注意してほしいのは、 ORM ではオブジェクトを
単一のキャッシュからの参照として利用するため、 'as_object' を true にして使う場合、子 (children) が他の場所で取得済みのオブジェクトにも追加されるということです。
related メソッドで、入れ子集合用の命令を実行する際、リレーションのオブジェクトを取得することができます。これは
通常の Orm モデルのクエリのものと同じものです。
静的 |
いいえ |
パラメータ |
パラメータ |
デフォルト |
説明 |
$related |
必須 |
含めるリレーションの名前 |
$conditions |
array() |
リレーションのオブジェクトを取得する際に渡したい何らかの条件 |
|
戻り値 |
Model_Nestedset 、メソッドチェーン用 |
例 |
// リレーション 'child' のオブジェクトを結果に含める
$tree = $bill->related('child')->dump_tree();
// $ben の子孫を取得
$descendants = $ben->descendants()->related('child')->get();
// これには child のリレーションは含まれ「ない」!
$descendants = $ben->related('child')->descendants()->get();
|
このメソッドを入れ子集合用の操作 (たとえば decendants() のような) とともに使いたい場合は常に、先にその操作を
行うようにしてください。さもなければ、含めた筈のリレーションが入れ子集合用操作の呼び出しで取り消されてしまうことになります!