これは、リレーショナル データベースでツリーを操作するための Laravel 4-10 パッケージです。
Laravel 11.0は v6.0.4 以降サポートされています
Laravel 10.0は v6.0.2 以降サポートされています
Laravel 9.0 はv6.0.1 以降サポートされています
Laravel 8.0はv6.0.0以降サポートされています
Laravel 5.7、5.8、6.0、7.0 はv5 以降サポートされています
Laravel 5.5、5.6 はv4.3 以降サポートされています
Laravel 5.2、5.3、5.4 はv4 以降サポートされています
Laravel 5.1 はv3 でサポートされています
Laravel 4 はv2 でサポートされています
コンテンツ:
理論
ドキュメント
ノードの挿入
ノードの取得
ノードの削除
一貫性のチェックと修正
スコーピング
要件
インストール
ネストされたセットまたはネストされたセット モデルは、階層データをリレーショナル テーブルに効果的に格納する方法です。ウィキペディアより:
ネストされたセット モデルでは、ツリー トラバーサルに従ってノードに番号を付けます。ツリー トラバーサルでは、各ノードを 2 回訪問し、訪問順に番号を割り当て、両方の訪問時に番号を割り当てます。これにより、各ノードに 2 つの数値が残り、2 つの属性として保存されます。クエリのコストが低くなるため、これらの数値を比較することで階層メンバーシップをテストできます。更新には再番号付けが必要なため、コストがかかります。
NSM は、ツリーがほとんど更新されない場合に良好なパフォーマンスを示します。関連ノードの取得が高速になるように調整されています。ショップの複数の深さのメニューやカテゴリを構築するのに最適です。
Category
というモデルがあるとします。 $node
変数は、そのモデルと操作しているノードのインスタンスです。新しいモデルまたはデータベースからのモデルを使用できます。
ノードには、完全に機能し、積極的にロードできる次の関係があります。
ノードはparent
に属します
ノードには多くのchildren
あります
ノードには多くのancestors
があります
ノードには多数のdescendants
があります
ノードの移動と挿入にはいくつかのデータベース クエリが含まれるため、トランザクションを使用することを強くお勧めします。
重要! v4.2.0 以降、トランザクションは自動的に開始されません
もう 1 つの重要な点は、モデルでsave
押すまで構造操作が延期されることです (一部のメソッドは暗黙的にsave
を呼び出し、操作のブール結果を返します)。
モデルが正常に保存された場合、それはノードが移動されたことを意味するものではありません。アプリケーションがノードの位置が実際に変更されたかどうかに依存している場合は、 hasMoved
メソッドを使用します。
if ($node->save()) {$moved = $node->hasMoved(); }
単純にノードを作成すると、そのノードはツリーの末尾に追加されます。
カテゴリ::create($attributes); // rootとして保存
$node = 新しいカテゴリ($attributes);$node->save(); // rootとして保存
この場合、ノードはルートとみなされます。これは、ノードに親がないことを意味します。
// #1 暗黙的な save$node->saveAsRoot(); // #2 明示的な save$node->makeRoot()->save();
ノードはツリーの最後に追加されます。
ノードを他のノードの子にしたい場合は、それを最後の子または最初の子にすることができます。
次の例では、 $parent
既存のノードです。
ノードを追加するにはいくつかの方法があります。
// #1 遅延された insert$node->appendToNode($parent)->save() を使用します;// #2 親ノードを使用する $parent->appendNode($node);// #3 親の子の関係を使用します$parent- >children()->create($attributes);// #5 ノードの親関係を使用する$node->parent()->associate($parent)->save();// #6 親属性の使用$node->parent_id = $parent->id;$node->save();// #7 静的メソッドの使用Category::create($attributes, $parent);
先頭に追加する方法は 2 つだけです。
// #1$node->prependToNode($parent)->save();// #2$parent->prependNode($node);
次の方法を使用して、 $node
$neighbor
ノードの隣接ノードにすることができます。
$neighbor
存在する必要があります。ターゲット ノードは新しいものでもかまいません。ターゲット ノードが存在する場合は、新しい位置に移動され、必要に応じて親が変更されます。
# 明示的 save$node->afterNode($neighbor)->save();$node->beforeNode($neighbor)->save();# 暗黙的 save$node->insertAfterNode($neighbor);$node-> insertBeforeNode($neighbor);
ノードで静的メソッドcreate
を使用すると、属性にchildren
キーが含まれているかどうかがチェックされます。存在する場合は、さらに多くのノードが再帰的に作成されます。
$node = カテゴリ::create(['name' => 'Foo','children' => [ ['名前' => 'バー','子供' => [ [ '名前' => 'バズ' ], ]、 ]、 ]、 ]);
$node->children
作成された子ノードのリストが含まれるようになりました。
ツリーを簡単に再構築できます。これは、ツリーの構造を一括変更する場合に便利です。
カテゴリ::rebuildTree($data, $delete);
$data
ノードの配列です。
$data = [ [ 'id' => 1、'name' => 'foo'、'children' => [ ... ] ]、 [ '名前' => 'バー' ], ];
foo
という名前のノードに指定された ID があります。これは、既存のノードが入力されて保存されることを意味します。ノードが存在しない場合はModelNotFoundException
がスローされます。また、このノードには、ノードの配列でもあるchildren
指定されています。これらは同じ方法で処理され、ノードfoo
の子として保存されます。
bar
は主キーが指定されていないため、作成されます。
$delete
、既に存在するが$data
に存在しないノードを削除するかどうかを示します。デフォルトでは、ノードは削除されません。
4.2.8 では、サブツリーを再構築できます。
カテゴリ::rebuildSubtree($root, $data);
これにより、ツリーの再構築が$root
ノードの子孫に制限されます。
場合によっては、ターゲット ノードの ID である$id
変数を使用します。
祖先はノードへの親のチェーンを作成します。現在のカテゴリのブレッドクラムを表示するのに役立ちます。
子孫はサブツリー内のすべてのノード、つまりノードの子、子の子などです。
祖先と子孫の両方を熱心にロードできます。
// 先祖へのアクセス$node->ancestors;// 子孫へのアクセス$node->descendants;
カスタム クエリを使用して祖先と子孫をロードすることができます。
$result = カテゴリ::ancestorsOf($id);$result = カテゴリ::ancestorsAndSelf($id);$result = カテゴリ::descendantsOf($id);$result = カテゴリ::descendantsAndSelf($id);
ほとんどの場合、祖先をレベル順に並べる必要があります。
$result = カテゴリ::defaultOrder()->ancestorsOf($id);
祖先のコレクションを積極的にロードできます。
$categories = カテゴリ::with('ancestors')->paginate(30);// ブレッドクラムのビュー内:@foreach($categories as $i => $category) <small>{{ $category->ancestors->count() ? implode(' > ', $category->ancestors->pluck('name')->toArray()) : 'トップレベル' }}</small><br> {{ $カテゴリ->名前 }} @endforeach
兄弟とは、同じ親を持つノードです。
$result = $node->getSiblings();$result = $node->siblings()->get();
次の兄弟のみを取得するには:
// ノードの直後にある兄弟を取得します$result = $node->getNextSibling();// ノードの後にあるすべての兄弟を取得します$result = $node->getNextSiblings();// を使用してすべての兄弟を取得しますquery$result = $node->nextSiblings()->get();
以前の兄弟を取得するには:
// ノードの直前にある兄弟を取得します$result = $node->getPrevSibling();// ノードの前にあるすべての兄弟を取得します$result = $node->getPrevSiblings();// を使用してすべての兄弟を取得しますquery$result = $node->prevSiblings()->get();
各カテゴリにhas many
と想像してください。 Ie HasMany
関係が確立されます。 $category
のすべての商品とその子孫をすべて入手するにはどうすればよいでしょうか?簡単!
// 子孫の ID を取得します$categories = $category->descendants()->pluck('id');// カテゴリ自体の ID を含めます$categories[] = $category->getKey();// 商品を取得します$goods = Goods::whereIn('category_id', $categories)->get();
ノードがどのレベルにあるかを知る必要がある場合は、次のようにします。
$結果 = カテゴリ::withDepth()->find($id);$深さ = $結果->深さ;
ルート ノードはレベル 0 になります。ルート ノードの子のレベルは 1 などになります。
指定したレベルのノードを取得するには、 having
制約を適用します。
$result = カテゴリ::withDepth()->having('深さ', '=', 1)->get();
重要!これはデータベース厳密モードでは機能しません
すべてのノードは内部的に厳密に編成されています。デフォルトでは順序は適用されないため、ノードはランダムな順序で表示されますが、これはツリーの表示には影響しません。ノードはアルファベットまたはその他のインデックスで並べ替えることができます。
ただし、場合によっては、階層順序が重要です。これは祖先を取得するために必要であり、メニュー項目の注文に使用できます。
ツリー順序を適用するには、 defaultOrder
メソッドが使用されます。
$result = カテゴリ::defaultOrder()->get();
逆の順序でノードを取得できます。
$result = カテゴリ::reversed()->get();
親内でノードを上下に移動してデフォルトの順序に影響を与えるには:
$bool = $node->down();$bool = $node->up();// ノードを 3 つの兄弟分シフトします$bool = $node->down(3);
操作の結果は、ノードの位置が変更されたかどうかを示すブール値です。
クエリ ビルダーに適用できるさまざまな制約:
whereIsRoot() はルート ノードのみを取得します。
hasParent() を使用して非ルート ノードを取得します。
whereIsLeaf() は葉のみを取得します。
hasChildren() を使用して非葉ノードを取得します。
whereIsAfter($id) は、指定された ID を持つノードの後にあるすべてのノード (兄弟だけでなく) を取得します。
whereIsBefore($id) は、指定された ID を持つノードの前にあるすべてのノードを取得します。
子孫の制約:
$result = カテゴリ::whereDescendantOf($node)->get();$result = カテゴリ::whereNotDescendantOf($node)->get();$result = カテゴリ::orWhereDescendantOf($node)->get() ;$result = カテゴリ::orWhereNotDescendantOf($node)->get();$result = category::whereDescendantAndSelf($id)->get();// ターゲット ノードを結果に含めます set$result = category::whereDescendantOrSelf($node)->get();
祖先制約:
$result = カテゴリ::whereAncestorOf($node)->get();$result = カテゴリ::whereAncestorOrSelf($id)->get();
$node
、モデルまたはモデル インスタンスの主キーのいずれかになります。
ノードのセットを取得したら、それをツリーに変換できます。例えば:
$tree = カテゴリ::get()->toTree();
これにより、セット内のすべてのノードのparent
とchildren
関係が満たされ、再帰アルゴリズムを使用してツリーをレンダリングできます。
$nodes = カテゴリ::get()->toTree();$traverse = function ($categories, $prefix = '-') use (&$traverse) {foreach ($categories as $category) {echo PHP_EOL.$接頭辞。 '.$category->name;$traverse($category->children, $prefix.'-'); } };$traverse($nodes);
これにより、次のようなものが出力されます。
- Root -- Child 1 --- Sub child 1 -- Child 2 - Another root
また、フラット ツリー、つまり子ノードが親ノードの直後にあるノードのリストを構築することもできます。これは、カスタム順序 (つまり、アルファベット順) でノードを取得し、ノードの反復処理に再帰を使用したくない場合に役立ちます。
$nodes = カテゴリ::get()->toFlatTree();
前の例では次のように出力されます。
Root Child 1 Sub child 1 Child 2 Another root
場合によっては、ツリー全体をロードする必要がなく、特定のノードの一部のサブツリーだけをロードする必要がある場合があります。それは次の例に示されています。
$root = カテゴリ::descendantsAndSelf($rootId)->toTree()->first();
単一のクエリで、サブツリーのルートと、 children
リレーションを介してアクセスできるそのすべての子孫を取得します。
$root
ノード自体が必要ない場合は、代わりに次の操作を実行します。
$tree = カテゴリ::子孫Of($rootId)->toTree($rootId);
ノードを削除するには:
$node->delete();
重要!そのノードが持つ子孫もすべて削除されます。
重要!ノードはモデルとして削除する必要があります。次のようなクエリを使用してノードを削除しないでください。
カテゴリ::where('id', '=', $id)->delete();
これでは木が折れてしまいます!
SoftDeletes
特性はモデル レベルでもサポートされています。
ノードが他のノードの子孫であるかどうかを確認するには:
$bool = $node->isDescendantOf($parent);
ノードがルートであるかどうかを確認するには:
$bool = $node->isRoot();
その他のチェック:
$node->isChildOf($other);
$node->isAncestorOf($other);
$node->isSiblingOf($other);
$node->isLeaf()
ツリーが壊れているかどうか (つまり、構造上のエラーがあるかどうか) を確認できます。
$bool = カテゴリ::isBroken();
エラー統計を取得できます。
$data = カテゴリ::countErrors();
次のキーを含む配列が返されます。
oddness
-- 間違ったlft
値とrgt
値のセットを持つノードの数
duplicates
-- 同じlft
またはrgt
値を持つノードの数
wrong_parent
-- lft
値とrgt
値に対応しない無効なparent_id
値を持つノードの数
missing_parent
-- 存在しないノードを指すparent_id
を持つノードの数
v3.1 以降、ツリーを修正できるようになりました。 parent_id
列からの継承情報を使用して、適切な_lft
および_rgt
値がすべてのノードに設定されます。
ノード::fixTree();
Menu
モデルとMenuItems
あると想像してください。これらのモデル間には 1 対多の関係が設定されています。 MenuItem
モデルを結合するためのmenu_id
属性があります。 MenuItem
ネストされたセットが組み込まれています。 menu_id
属性に基づいて各ツリーを個別に処理したいことは明らかです。これを行うには、この属性をスコープ属性として指定する必要があります。
保護された関数 getScopeAttributes() {return [ 'menu_id' ]; }
ただし、カスタム クエリを実行するには、スコープ設定に使用される属性を指定する必要があります。
MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OKMenuItem::descendantsOf($id)->get(); // 誤り: 他のscopeMenuItem::scoped([ 'menu_id' => 5 ])->fixTree(); からノードを返します。 // わかりました
モデル インスタンスを使用してノードをリクエストすると、そのモデルの属性に基づいてスコープが自動的に適用されます。
$node = MenuItem::findOrFail($id);$node->siblings()->withDepth()->get(); // わかりました
インスタンスを使用してスコープ指定されたクエリ ビルダーを取得するには:
$node->newScopedQuery();
積極的にロードする場合は常にスコープ付きクエリを使用します。
MenuItem::scoped([ 'menu_id' => 5])->with('descendants')->findOrFail($id); // OKMenuItem::with('descendants')->findOrFail($id); // 間違っている
PHP >= 5.4
Laravel >= 4.1
ツリーを破損から守るために、トランザクションをサポートするデータベース (MySql の InnoDb など) を使用することを強くお勧めします。
パッケージをインストールするには、ターミナルで次のようにします。
composer require kalnoy/nestedset
Laravel 5.5 以降のユーザーの場合:
Schema::create('table', function (Blueprint $table) {...$table->nestedSet(); });// 列を削除するにはSchema::table('table', function (Blueprint $table) {$table->dropNestedSet(); });
以前の Laravel バージョンの場合:
...KalnoyNestedsetNestedSet を使用します。 Schema::create('table', function (Blueprint $table) {...NestedSet::columns($table); });
列を削除するには:
...KalnoyNestedsetNestedSet を使用します。 Schema::table('table', function (Blueprint $table) { NestedSet::dropColumns($table); });
モデルはKalnoyNestedsetNodeTrait
特性を使用して、ネストされたセットを有効にする必要があります。
KalnoyNestedsetNodeTrait を使用;class Foo extends Model {use NodeTrait; }
以前の拡張機能で別の列セットが使用されていた場合は、モデル クラスで次のメソッドをオーバーライドするだけで済みます。
パブリック関数 getLftName() {「左」を返す; }パブリック関数getRgtName() {「右」に戻ります; }パブリック関数 getParentIdName() {「親」を返す; }// 親 ID 属性を指定します。 mutatorpublic function setParentAttribute($value) {$this->setParentIdAttribute($value); }
ツリーにparent_id
情報が含まれている場合は、スキーマに次の 2 つの列を追加する必要があります。
$table->unsignedInteger('_lft');$table->unsignedInteger('_rgt');
モデルを設定した後は、 _lft
と_rgt
列を埋めるようにツリーを修正するだけです。
MyModel::fixTree();
著作権 (c) 2017 アレクサンダー カルノイ
本ソフトウェアおよび関連ドキュメント ファイル (以下「ソフトウェア」) のコピーを入手した人には、使用、コピー、変更、マージする権利を含むがこれらに限定されない、制限なくソフトウェアを取り扱う許可が、ここに無償で与えられます。 、以下の条件を条件として、本ソフトウェアのコピーを出版、配布、サブライセンス、および/または販売すること、および本ソフトウェアが提供される人物にそれを許可すること。
上記の著作権表示およびこの許可通知は、ソフトウェアのすべてのコピーまたは主要部分に含まれるものとします。
ソフトウェアは「現状のまま」提供され、明示的か黙示的かを問わず、商品性、特定目的への適合性、および非侵害の保証を含むがこれらに限定されない、いかなる種類の保証も行われません。いかなる場合においても、作者または著作権所有者は、契約行為、不法行為、またはその他の行為であるかどうかにかかわらず、ソフトウェアまたはソフトウェアの使用またはその他の取引に起因または関連して生じる、いかなる請求、損害、またはその他の責任に対しても責任を負わないものとします。ソフトウェア。