laravel-nestedset : 올바른 자세의 무제한 다단계 분류
laravel-nestedset은 패키지 larvel4-5에서 플러그인의 관계형 데이터베이스 트리 탐색이다
내용 :
- 중첩 된 세트 모델 소개
- 설치 요구 사항
- 설치
- 시작
- 파일 마이그레이션
- 노드 삽입
- 노드를 가져옵니다
- 삭제 노드
- 일관성 검사 및 수리
- 범위
중첩 된 세트 모델 소개
중첩 된 세트 모델은 빠르고하고 아무리 많은 레이어와 같은 트리로 재귀 쿼리를 필요로하지 않습니다 주문 트리를 구현하기위한 현명한 방법이다, 당신은 노드의 모든 자손을 얻기 위해 단 하나 개의 쿼리를 사용할 수 있으며, 단점은 그것이 그 복잡한 SQL 문을 수행해야하지만, 이러한이 플러그인 내에서 처리하고, 삭제, 이동, 삽입! 위키 백과에 대한 자세한하세요! 중첩 된 세트 모델 과 중국어 번역! 중첩 된 세트 모델
설치 요구 사항
- PHP> = 5.4
- laravel> = 4.1
- V4.3 지원 향후 버전 Laravel - 5.5
- V4 버전 지원 Laravel-5.2,5.3,5.4
- v3 릴리즈가 지원 Laravel - 5.1
- V2 버전 지원 Laravel-4는 강력하게 잠재적 인 데이터 손상을 방지하기 위해 (MySQL의 InnoDB의 같은) 엔진의 기능을 지원 것들 데이터의 사용을 권장합니다.
설치
에서 composer.json
다음 코드 파일을 추가 :
"kalnoy/nestedset": "^4.3",
실행 composer install
하여 설치.
또는 직접 명령 행 입력에서
composer require kalnoy/nestedset
버전 기록을 클릭하여 설치합니다 더 많은 버전을
시작
파일 마이그레이션
당신이 사용할 수있는 NestedSet
클래스를 columns
방법의 기본 이름으로 필드를 추가 :
...
use Kalnoy\Nestedset\NestedSet;
Schema::create('table', function (Blueprint $table) {
...
NestedSet::columns($table);
});
삭제 필드 :
...
use Kalnoy\Nestedset\NestedSet;
Schema::table('table', function (Blueprint $table) {
NestedSet::dropColumns($table);
});
기본 필드는 _lft
이름 : _rgt
,, parent_id
, 소스 코드는 다음과 같습니다 :
public static function columns(Blueprint $table)
{
$table->unsignedInteger(self::LFT)->default(0);
$table->unsignedInteger(self::RGT)->default(0);
$table->unsignedInteger(self::PARENT_ID)->nullable();
$table->index(static::getDefaultColumns());
}
모델
당신은 모델 사용할 필요가 Kalnoy\Nestedset\NodeTrait
중첩 된 세트를 구현하는 특성을
use Kalnoy\Nestedset\NodeTrait;
class Foo extends Model {
use NodeTrait;
}
기존의 로컬 데이터 마이그레이션
다른 중첩 된 세트 모델 라이브러리에서 마이그레이션
public function getLftName()
{
return 'left';
}
public function getRgtName()
{
return 'right';
}
public function getParentIdName()
{
return 'parent';
}
// Specify parent id attribute mutator
public function setParentAttribute($value)
{
$this->setParentIdAttribute($value);
}
다른 모델 라이브러리에서 마이그레이션 부모 - 자식 관계가
데이터베이스가 나무가 포함 된 경우 parent_id
필드 정보를, 당신은 당신의 청사진 문서에 다음과 같은 두 개의 열 필드를 추가해야합니다 :
$table->unsignedInteger('_lft');
$table->unsignedInteger('_rgt');
모델을 설정 한 후에 만 채우기에 트리를 수정해야 _lft
하고 _rgt
필드 :
MyModel::fixTree();
관계
노드가 완벽하게 작동하고 미리로드되어, 다음과 같은 기능이 있습니다 :
- 노드는 부모에 속하는
- 노드는 많은 아이들이있다
- 노드는 여러 조상을 가지고
- 노드는 많은 자손을 가지고
우리는 분류 모델을 가지고 가정, $ 노드는 모델의 인스턴스 변수는 우리의 작업의 노드 (노드)입니다. 그것은 새로운 노드가 생성하거나 데이터베이스 노드에서 제거 될 수 있습니다
삽입 노드 (노드)
노드를 삽입하거나 이동할 때마다 몇 가지 데이터베이스 작업이 모두 좋습니다 트랜잭션을 실행합니다.
주의! V4.2.0 버전이 자동으로 열려있는 트랜잭션이 아니며, 구조 작업의 다른 노드는 모델에 저장 수동으로 수행 할 필요는 없지만, 일부는 저장 및 작동 상태로 복귀의 부울 결과를 수행하는 방법을 숨겨집니다.
만들기 노드 (노드)
간단한 노드를 만들 때, 그것은 나무의 끝에 추가됩니다.
Category::create($attributes); // 自动save为一个根节点(root)
또는
$node = new Category($attributes);
$node->save(); // save为一个根节点(root)
다음은 노드가 부모가없는 것을 의미 루트로 설정
루트에 기존의 노드
// #1 隐性 save
$node->saveAsRoot();
// #2 显性 save
$node->makeRoot()->save();
부모 노드 또는 지정된 프론트 엔드의 마지막에 자식 노드를 추가합니다
당신이 자식 노드를 추가 할 경우, 부모 노드 또는 마지막 아이의 첫 번째 자식 노드를 추가 할 수 있습니다. * 다음과 같은 예에서, 기존의 노드로$parent
부모 노드에있어서의 끝에 추가 :
// #1 使用延迟插入
$node->appendToNode($parent)->save();
// #2 使用父节点
$parent->appendNode($node);
// #3 借助父节点的children关系
$parent->children()->create($attributes);
// #5 借助子节点的parent关系
$node->parent()->associate($parent)->save();
// #6 借助父节点属性
$node->parent_id = $parent->id;
$node->save();
// #7 使用静态方法
Category::create($attributes, $parent);
부모 노드의 선단에 추가
// #1
$node->prependToNode($parent)->save();
// #2
$parent->prependNode($node);
노드는 앞에 삽입 또는 지정된 노드의 후면되고
당신은 다음 방법을 사용하여 $node
지정된 노드 추가 $neighbor
인접 노드를
$neighbor
있어야합니다, $node
당신은 새로운 노드를 만들 수 있습니다, 또는 경우, 기존 될 수 있습니다 $node
이미 존재하는 노드가이 새로운 위치로 이동합니다 $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' => [
[
'name' => 'Bar',
'children' => [
[ 'name' => 'Baz' ],
],
],
],
]);
이제 $node->children
생성 된 노드 세트가 포함되어 있습니다.
배열은 나무를 다시
당신은 쉽게 트리 구조의 수정을 많이 절약 할 매우 유용한 트리를 재구성 할 수 있습니다. Category::rebuildTree($data, $delete);
$data
어레이 주제 노드
$data = [
[ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ],
[ 'name' => 'bar' ],
];
최고 가지고 name
에 대한 foo
지정된 한 노드, id
노드가 존재하지 않는 경우 노드의 기존 대신이 던져처럼, 채워집니다 ModelNotFoundException
또한,이 노드뿐만 아니라 children
배열을, 배열은 같은 방식으로 할 것 추가받는 foo
내부 노드. bar
노드는이 생성됩니다, 존재하지 않는, 기본 키가 없습니다. $delete
대표 데이터베이스를 삭제할지 여부 것은 이미 존재하지만 $data
데이터에 존재하지 않는, 기본은 삭제되지 않습니다.
이후 재건 하위 트리 버전 4.3.8에는 서브 트리를 재 구축 할 수 있습니다
Category::rebuildSubtree($root, $data);
이 단지 $ 루트 하위 트리의 재건을 제한 할 것이다
검색 노드
우리는 변수 $ id를 사용해야하는 경우에는 기본 키 대상 노드의 ID를 나타냅니다
조상과 후손
선조는 빵의 전류 형태의 디스플레이를 위해 도움이 부스러기 부모 노드의 체인을 생성한다. 자손은 부모 노드의 모든 자식 노드입니다. 조상과 후손이 사전로드되어 있습니다.
// Accessing ancestors
$node->ancestors;
// Accessing descendants
$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()) : 'Top Level' </small><br>
$category->name
@endforeach
조상은 name
모든 스플 라이스에서> 문자열, 배열로 꺼내.
형제
우리는 같은 부모는 형제 노드 상호이라고했다
$result = $node->getSiblings();
$result = $node->siblings()->get();
인접 형제 뒤에 가져 오기 :
// 获取相邻的下一个兄弟节点
$result = $node->getNextSibling();
// 获取后面的所有兄弟节点
$result = $node->getNextSiblings();
// 使用查询获得所有兄弟节点
$result = $node->nextSiblings()->get();
인접 형제 앞에 가져 오기 :
// 获取相邻的前一个兄弟节点
$result = $node->getPrevSibling();
// 获取前面的所有兄弟节点
$result = $node->getPrevSiblings();
// 使用查询获得所有兄弟节点
$result = $node->prevSiblings()->get();
관련 모델 테이블에 액세스
각 카테고리는 많은 제품을 가지고 있으며, hasMany의 관계가 설정되었음을 가정, 어떻게 간단하게는 전체를 위하여, 전체는 미래 세대에 제품의 $ 범주를 얻을?
// 获取后代的id
$categories = $category->descendants()->pluck('id');
// 包含Category本身的id
$categories[] = $category->getKey();
// 获得goods
$goods = Goods::whereIn('category_id', $categories)->get();
노드는 깊이를 포함 (깊이)
당신은 그 수준의 액세스 노드를 알 필요가있는 경우 :
$result = Category::withDepth()->find($id);
$depth = $result->depth;
사용할 수 루트 노드 (루트), 레이어 0 (레벨 0), 부 - 루트 노드는 제 1 층이 (레벨 1) 인 having
특정 노드 계층을 구하는 제약
$result = Category::withDepth()->having('depth', '=', 1)->get();
이 데이터베이스 엄격 모드에서 사용할 수 없습니다 있습니다
기본 정렬
모든 노드는 엄격한 조직 내에서 기본적으로 어떤 순서없는, 노드는 임의 쇼 당신은 알파벳 순서 및 정렬 다른 노드에서, 이것의 영향을 보여주고, 할 수있다.
그러나 경우에 따라서는 조상과 메뉴 순서를 얻기위한 유용 계층 구조를 표시하는 것이 필요하다.
사용 deaultOrder 나무의 종류를 사용합니다 : $result = Category::defaultOrder()->get();
또한 역순를 사용할 수 있습니다 : $result = Category::reversed()->get();
정렬 기본값을 변경하기까지 내부 아래로 부모 노드로 이동하자 :
$bool = $node->down();
$bool = $node->up();
// 向下移动3个兄弟节点
$bool = $node->down(3);
부울 값의 노드의 동작 위치를 변경할 것인지로 되돌아갑니다
강제
많은 제약이 쿼리 빌더에서 사용할 수 있습니다 :
- whereIsRoot ()는 루트 노드를 가져옵니다;
- whereIsAfter ($ ID는) 특정 노드 (뿐만 아니라 형제 자매)의 id의 뒷면에있는 모든 노드를 가져옵니다.
- whereIsBefore ($ ID는) 특정 노드 (뿐만 아니라 형제 자매)의 id의 앞에 모든 노드를 가져옵니다.
조상 제약
$result = Category::whereAncestorOf($node)->get();
$result = Category::whereAncestorOrSelf($id)->get();
$node
한 예는 주요 키 또는 모델의 모델이 될 수있다
자손 제약
$result = Category::whereDescendantOf($node)->get();
$result = Category::whereNotDescendantOf($node)->get();
$result = Category::orWhereDescendantOf($node)->get();
$result = Category::orWhereNotDescendantOf($node)->get();
$result = Category::whereDescendantAndSelf($id)->get();
//结果集合中包含目标node自身
$result = Category::whereDescendantOrSelf($node)->get();
트리 구축
결과 집합의 노드를 취득 후, 우리는 예를 들어, 나무로 변환 할 수 있습니다 : $tree = Category::get()->toTree();
이는 각 노드에서 부모와 자녀의 관계를 추가 할 것입니다, 당신은 나무를 렌더링하는 재귀 알고리즘을 사용할 수 있습니다 :
$nodes = Category::get()->toTree();
$traverse = function ($categories, $prefix = '-') use (&$traverse) {
foreach ($categories as $category) {
echo PHP_EOL.$prefix.' '.$category->name;
$traverse($category->children, $prefix.'-');
}
};
$traverse($nodes);
그것은 다음과 유사한 출력과 같이 될 것입니다 :
- Root
-- Child 1
--- Sub child 1
-- Child 2
- Another root
평평한 나무의 건설
당신은 평면 트리를 구성 할 수 있습니다 : 하위 노드가 직접 부모 노드 뒤에 배치됩니다. 사용자 정의 정렬 노드를 얻을 할 때 루프에 노드를 재귀를 사용하지 유용합니다. $nodes = Category::get()->toFlatTree();
이것의 예는 다음과 같은 출력하기 전에 될 것입니다 :
Root
Child 1
Sub child 1
Child 2
Another root
하위 트리의 건설
때때로 당신은 전체 트리하지만 특정 하위 트리를로드 할 필요가 없습니다 : $root = Category::descendantsAndSelf($rootId)->toTree()->first();
간단한 쿼리 우리는 루트 및 어린이의 사용 사이의 관계를 얻을 수있는 하위 트리의 모든 자손을 얻을
당신이 $ 루트 노드 자체가 필요하지 않은 경우, 당신은 할 수 있습니다 : $tree = Category::descendantsOf($rootId)->toTree($rootId);
삭제 노드
노드 삭제 :
$node->delete();
** 참고! ** 모든 자손 노드는 참고 삭제됩니다! 노드가 동일한 모델에 제거해야합니다, 당신은 노드를 삭제하려면 다음 문을 사용할 수 없습니다 :
Category::where('id', '=', $id)->delete();
이 트리 구조 지원의 파괴 SoftDeletes
특성, 그리고 모델 층에서
도우미 메서드
노드를 확인하는 것은 다른 노드에 자식 노드 $bool = $node->isDescendantOf($parent);
루트 여부 확인 $bool = $node->isRoot();
기타 시험
- $ 노드 -> isChildOf ($ 기타);
- $ 노드 -> isAncestorOf ($ 기타);
- $ 노드 -> isSiblingOf ($ 기타);
- $ 노드 -> isLeaf ()
일관성을 확인
당신은 나무가 반지를 파괴 여부를 확인할 수 있습니다 $bool = Category::isBroken();
오류 통계를 얻을 : $data = Category::countErrors();
이 키에 대한 포함하는 배열을 반환
- 노드의 오류 번호의 LFT와 RGT 값 - oddness
- LFT RGT 값 또는 노드를 반복 수 - 중복
- wrong_parent는 - 왼쪽 RGT 값은 PARENT_ID 잘못된 번호를 PARENT_ID 원인 노드에 해당하지 않는
- PARENT_ID 포함하는 노드를 존재하지 않는 부모 노드에 해당하는 번호 - missing_parent
수리 트리
버전 3.1 지원 향후 복원 트리에서, 정보의 상속 PARENT_ID 필드를 통해 각 노드는 적절한 값 LFT와 RGT를 설정합니다 Node::fixTree();
범위 (범위)
. 당신이 한 수많은 모델과 메뉴 아이템이 있다고 가정 사이에 일대 다 관계입니다. 메뉴 아이템은 menu_id 속성이 중첩 된 세트 모델을 실현. 물론 당신이 재산 기반이 이러한 기능을 달성하기 위해, 개별적으로 각각의 나무를 menu_id 처리하려면, 우리는 범위 속성에 속성을 menu_id 지정해야합니다.
protected function getScopeAttributes()
{
return [ 'menu_id' ];
}
이제 우리는 사용자 지정 쿼리를 구현해야, 우리는 재산의 범위를 제한 할 필요를 제공해야합니다.
MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OK
MenuItem::descendantsOf($id)->get(); // WRONG: returns nodes from other scope
MenuItem::scoped([ 'menu_id' => 5 ])->fixTree();
그러나 자동 기반 모델 인스턴스 쿼리 노드 범위를 사용하면 선택한 노드를 삭제하는 범위 속성 세트를 제한합니다. 예를 들면 :
$node = MenuItem::findOrFail($id);
$node->siblings()->withDepth()->get(); // OK
예 쿼리는 선택의 삭제를 얻을 수 있습니다 : $node->newScopedQuery();
키가 범위를 사용하여 수집 모델을 필요로하지 않을 때, 그 주
$node = MenuItem::findOrFail($id); // OK
$node = MenuItem::scoped([ 'menu_id' => 5 ])->findOrFail(); // OK, 但是多余