관계형 데이터베이스의 트리 작업을 위한 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에서 지원됩니다.
내용물:
이론
선적 서류 비치
노드 삽입
노드 검색
노드 삭제
일관성 검사 및 수정
범위 지정
요구사항
설치
중첩 세트 또는 중첩 세트 모델은 관계형 테이블에 계층적 데이터를 효과적으로 저장하는 방법입니다. 위키피디아에서:
중첩 세트 모델은 각 노드를 두 번 방문하고 방문 순서대로 번호를 할당하는 트리 순회에 따라 노드에 번호를 매기는 것입니다. 이렇게 하면 각 노드에 대해 두 개의 숫자가 남고 두 개의 속성으로 저장됩니다. 쿼리 비용이 저렴해집니다. 이러한 숫자를 비교하여 계층 구조 멤버십을 테스트할 수 있습니다. 업데이트하려면 번호를 다시 매겨야 하므로 비용이 많이 듭니다.
NSM은 트리가 거의 업데이트되지 않을 때 좋은 성능을 보여줍니다. 관련 노드를 빠르게 가져오도록 조정되었습니다. 다양한 깊이의 메뉴나 상점 카테고리를 구축하는 데 이상적입니다.
Category 모델이 있다고 가정합니다. $node 변수는 해당 모델의 인스턴스이자 우리가 조작하는 노드입니다. 새로운 모델일 수도 있고 데이터베이스의 모델일 수도 있습니다.
노드에는 완전히 작동하고 즉시 로드할 수 있는 다음과 같은 관계가 있습니다.
노드는 parent 에 속함
노드에 children 많음
노드에는 많은 ancestors 있습니다
노드에 많은 descendants 있음
노드 이동 및 삽입에는 여러 데이터베이스 쿼리가 포함되므로 트랜잭션을 사용하는 것이 좋습니다.
중요한! v4.2.0부터 트랜잭션이 자동으로 시작되지 않습니다.
또 다른 중요한 참고 사항은 구조적 조작이 모델에 save 될 때까지 연기된다는 것입니다(일부 메소드는 암시적으로 save 호출하고 작업의 부울 결과를 반환합니다).
모델이 성공적으로 저장되었다고 해서 노드가 이동되었다는 의미는 아닙니다. 애플리케이션이 노드가 실제로 위치를 변경했는지 여부에 따라 달라지는 경우 hasMoved 메소드를 사용하십시오.
if ($node->save()) {$moved = $node->hasMoved();
}단순히 노드를 생성하면 트리 끝에 추가됩니다.
카테고리::생성($attributes); // 루트로 저장됨
$node = 새 카테고리($attributes);$node->save(); // 루트로 저장됨
이 경우 노드는 루트 로 간주되며 이는 상위 노드가 없음을 의미합니다.
// #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 상위 관계 사용 attribute$node->parent_id = $parent->id;$node->save();// #7 정적 메서드 사용Category::create($attributes, $parent);
앞에 추가하는 방법은 몇 가지뿐입니다.
// #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 = Category::create(['name' => 'Foo','children' => [
['이름' => '바','아이들' => [
[ '이름' => '바즈' ],
],
],
],
]); $node->children 이제 생성된 하위 노드 목록이 포함됩니다.
트리를 쉽게 다시 만들 수 있습니다. 이는 트리 구조를 대량으로 변경하는 데 유용합니다.
카테고리::rebuildTree($data, $delete);
$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 = Category::ancestorsOf($id);$result = Category::ancestorsAndSelf($id);$result = Category::descendantsOf($id);$result = Category::descendantsAndSelf($id);
대부분의 경우, 레벨에 따라 조상을 정렬해야 합니다.
$result = Category::defaultOrder()->ancestorsOf($id);
상위 항목 컬렉션을 즉시 로드할 수 있습니다.
$categories = Category::with('ancestors')->paginate(30);// 이동 경로 보기:@foreach($categories as $i => $category)
<small>{{ $category->ancestors->count() ? implode(' > ', $category->ancestors->pluck('name')->toArray()) : '최상위 수준' }}</small><br>
{{ $category->이름 }}
@endforeach형제는 동일한 상위를 갖는 노드입니다.
$result = $node->getSiblings();$result = $node->siblings()->get();
다음 형제만 얻으려면:
// node$result = $node->getNextSibling();// node 바로 뒤에 있는 형제를 모두 가져옵니다$result = $node->getNextSiblings();// query$result = $node->nextSiblings()->get();
이전 형제를 얻으려면 다음을 수행하십시오.
// node$result = $node->getPrevSibling();// node 앞에 있는 모든 형제를 가져옵니다$result = $node->getPrevSiblings();// query$result = $node->prevSiblings()->get();
각 카테고리에 has many 상상해 보세요. 즉, HasMany 관계가 설정됩니다. $category 의 모든 상품과 모든 하위 항목을 어떻게 얻을 수 있습니까? 쉬운!
// 자손의 ID 가져오기$categories = $category->descendants()->pluck('id');// 카테고리 자체의 ID 포함$categories[] = $category->getKey();// 상품 가져오기 $goods = 상품::whereIn('category_id', $categories)->get();노드가 어느 수준에 있는지 알아야 하는 경우:
$result = 카테고리::withDepth()->find($id);$깊이 = $result->깊이;
루트 노드는 레벨 0이 됩니다. 루트 노드의 하위 노드는 레벨 1을 갖게 됩니다.
지정된 수준의 노드를 얻으려면 다음 having 조건을 적용할 수 있습니다.
$result = Category::withDepth()->having('깊이', '=', 1)->get();중요한! 데이터베이스 엄격 모드에서는 작동하지 않습니다.
모든 노드는 내부적으로 엄격하게 구성됩니다. 기본적으로 순서가 적용되지 않으므로 노드가 임의의 순서로 나타날 수 있으며 이는 트리 표시에 영향을 미치지 않습니다. 알파벳이나 기타 색인을 기준으로 노드를 정렬할 수 있습니다.
그러나 어떤 경우에는 계층적 순서가 필수적입니다. 조상 검색에 필요하며 메뉴 항목을 주문하는 데 사용할 수 있습니다.
트리 순서를 적용하려면 defaultOrder 메소드가 사용됩니다.
$result = Category::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 = Category::whereDescendantOf($node)->get();$result = Category::whereNotDescendantOf($node)->get();$result = Category::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 = Category::get()->toTree();$traverse = 함수 ($categories, $prefix = '-') 사용 (&$traverse) {foreach ($categories를 $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 = Category::descendantsAndSelf($rootId)->toTree()->first();
단일 쿼리에서 하위 트리의 루트와 children 관계를 통해 액세스할 수 있는 모든 하위 트리를 가져옵니다.
$root 노드 자체가 필요하지 않은 경우 대신 다음을 수행하십시오.
$tree = Category::descendantsOf($rootId)->toTree($rootId);
노드를 삭제하려면 다음 안내를 따르세요.
$노드->삭제();
중요한! 해당 노드에 있는 모든 하위 항목도 삭제됩니다!
중요한! 노드는 모델로 삭제되어야 합니다. 다음과 같은 쿼리를 사용하여 노드를 삭제 하지 마세요 .
카테고리::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 가 있다고 상상해 보세요. 이러한 모델 간에는 일대다 관계가 설정되어 있습니다. MenuItem 에는 모델을 함께 결합하기 위한 menu_id 속성이 있습니다. MenuItem 중첩된 세트를 통합합니다. menu_id 속성을 기반으로 각 트리를 개별적으로 처리하려는 것이 분명합니다. 그렇게 하려면 이 속성을 범위 속성으로 지정해야 합니다.
보호된 함수 getScopeAttributes()
{return [ '메뉴_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
라라벨 >= 4.1
손상 가능성으로부터 트리를 보호하려면 트랜잭션을 지원하는 데이터베이스(예: MySql의 InnoDb)를 사용하는 것이 좋습니다.
패키지를 설치하려면 터미널에서 다음을 수행하세요.
composer require kalnoy/nestedset
Laravel 5.5 이상 사용자의 경우:
Schema::create('table', function (Blueprint $table) {...$table->nestedSet();
});// columnsSchema::table('table', function (Blueprint $table) {$table->dropNestedSet();
});이전 Laravel 버전의 경우:
...KalnoyNestedsetNestedSet을 사용하세요.
Schema::create('table', function (Blueprint $table) {...NestedSet::columns($table);
});열을 삭제하려면 다음을 수행합니다.
...KalnoyNestedsetNestedSet을 사용하세요.
Schema::table('table', function (청사진 $table) {
NestedSet::dropColumns($table);
}); 모델은 중첩 세트를 활성화하려면 KalnoyNestedsetNodeTrait 특성을 사용해야 합니다.
KalnoyNestedsetNodeTrait 사용; 클래스 Foo는 모델 확장 {NodeTrait 사용;
}이전 확장에서 다른 열 집합을 사용한 경우 모델 클래스에서 다음 메서드를 재정의하면 됩니다.
공개 함수 getLftName()
{'왼쪽'을 반환;
}공개 함수 getRgtName()
{'오른쪽'을 반환;
}공개 함수 getParentIdName()
{'부모'를 반환;
}// 상위 ID 속성 지정 mutatorpublic 함수 setParentAttribute($value)
{$this->setParentIdAttribute($value);
} 트리에 parent_id 정보가 포함된 경우 스키마에 두 개의 열을 추가해야 합니다.
$table->unsignedInteger('_lft');$table->unsignedInteger('_rgt'); 모델을 설정한 후 _lft 및 _rgt 열을 채우도록 트리를 수정하기만 하면 됩니다.
MyModel::fixTree();
저작권 (c) 2017 Alexander Kalnoy
본 소프트웨어 및 관련 문서 파일("소프트웨어")의 사본을 취득한 모든 사람에게 사용, 복사, 수정, 병합에 대한 권리를 포함하되 이에 국한되지 않고 제한 없이 소프트웨어를 취급할 수 있는 권한이 무료로 부여됩니다. , 소프트웨어 사본을 게시, 배포, 재라이센스 부여 및/또는 판매하고, 소프트웨어를 제공받은 사람에게 다음 조건에 따라 그렇게 하도록 허용합니다.
위의 저작권 고지와 본 허가 고지는 소프트웨어의 모든 사본 또는 상당 부분에 포함됩니다.
소프트웨어는 상품성, 특정 목적에의 적합성 및 비침해에 대한 보증을 포함하되 이에 국한되지 않고 명시적이든 묵시적이든 어떠한 종류의 보증 없이 "있는 그대로" 제공됩니다. 어떠한 경우에도 작성자나 저작권 보유자는 계약, 불법 행위 또는 기타 행위로 인해 소프트웨어나 사용 또는 기타 거래와 관련하여 발생하는 모든 청구, 손해 또는 기타 책임에 대해 책임을 지지 않습니다. 소프트웨어.