@@ -68,7 +68,7 @@ Another important note is that __structural manipulations are deferred__ until y
6868hit ` save ` on model (some methods implicitly call ` save ` and return boolean result
6969of the operation).
7070
71- If model is successfully saved it doesn't mean that node has moved. If your application
71+ If model is successfully saved it doesn't mean that node was moved. If your application
7272depends on whether the node has actually changed its position, use ` hasMoved ` method:
7373
7474``` php
@@ -77,12 +77,17 @@ if ($node->save()) {
7777}
7878```
7979
80- #### Creating a new node
80+ #### Creating nodes
8181
82- When you just create a node, it will be appended to the end of the tree:
82+ When you simply creating a node, it will be appended to the end of the tree:
8383
8484``` php
85- Category::create($attributes);
85+ Category::create($attributes); // Saved as root
86+ ```
87+
88+ ``` php
89+ $node = new Category($attributes);
90+ $node->save(); // Saved as root
8691```
8792
8893In this case the node is considered a _ root_ which means that it doesn't have a parent.
@@ -103,7 +108,7 @@ The node will be appended to the end of the tree.
103108
104109If you want to make node a child of other node, you can make it last or first child.
105110
106- In following examples, ` $parent ` is some existing node.
111+ * In following examples, ` $parent ` is some existing node.*
107112
108113There are few ways to append a node:
109114
@@ -142,8 +147,8 @@ $parent->prependNode($node);
142147
143148You can make ` $node ` to be a neighbor of the ` $neighbor ` node using following methods:
144149
145- * Neighbor is existing node , target node can be fresh. If target node exists,
146- it will be moved to the new position and parent will be changed if required.*
150+ * ` $neighbor ` must exists , target node can be fresh. If target node exists,
151+ it will be moved to the new position and parent will be changed if it's required.*
147152
148153``` php
149154# Explicit save
@@ -155,20 +160,6 @@ $node->insertAfterNode($neighbor);
155160$node->insertBeforeNode($neighbor);
156161```
157162
158- #### Shifting a node
159-
160- To shift node up or down inside parent:
161-
162- ``` php
163- $bool = $node->down();
164- $bool = $node->up();
165-
166- // Shift node by 3 siblings
167- $bool = $node->down(3);
168- ```
169-
170- The result of the operation is boolean value of whether the node has changed the position.
171-
172163#### Building a tree from array
173164
174165When using static method ` create ` on node, it checks whether attributes contains
@@ -194,7 +185,7 @@ $node = Category::create([
194185
195186### Retrieving nodes
196187
197- In some cases we will use an ` $id ` variable which is an id of the target node.
188+ * In some cases we will use an ` $id ` variable which is an id of the target node.*
198189
199190#### Ancestors
200191
@@ -205,20 +196,20 @@ $result = $node->getAncestors();
205196// #2 Using a query
206197$result = $node->ancestors()->get();
207198
208- // #3 Getting ancestors by id of the node
199+ // #3 Getting ancestors by primary key
209200$result = Category::ancestorsOf($id);
210201```
211202
212203#### Descendants
213204
214205``` php
215- // #1 Using accessor
216- $result = $node->getDescendants() ;
206+ // #1 Using relationship
207+ $result = $node->descendants ;
217208
218- // #2 Using a query
209+ // #2 Using a query
219210$result = $node->descendants()->get();
220211
221- // #3 Getting ancestors by id of the node
212+ // #3 Getting descendants by primary key
222213$result = Category::descendantsOf($id);
223214```
224215
@@ -230,7 +221,7 @@ $result = $node->getSiblings();
230221$result = $node->siblings()->get();
231222```
232223
233- To get just next siblings ( [ default order ] ( #default-order ) is applied here) :
224+ To get only next siblings:
234225
235226``` php
236227// Get a sibling that is immediately after the node
@@ -243,7 +234,7 @@ $result = $node->getNextSiblings();
243234$result = $node->nextSiblings()->get();
244235```
245236
246- To get previous siblings ( [ reversed order ] ( #default-order ) is applied) :
237+ To get previous siblings:
247238
248239``` php
249240// Get a sibling that is immediately before the node
@@ -272,51 +263,6 @@ $categories[] = $category->getKey();
272263$goods = Goods::whereIn('category_id', $categories)->get();
273264```
274265
275- ### Deleting nodes
276-
277- To delete a node:
278-
279- ``` php
280- $node->delete();
281- ```
282-
283- ** IMPORTANT!** Any descendant that node has will also be deleted!
284-
285- ** IMPORTANT!** Nodes are required to be deleted as models, ** don't** try do delete them using a query like so:
286-
287- ``` php
288- Category::where('id', '=', $id)->delete();
289- ```
290-
291- This will brake the tree!
292-
293- ` SoftDeletes ` trait is supported, also on model level.
294-
295- ### Collection extension
296-
297- This package provides few helpful methods for collection of nodes. You can link nodes in plain collection like so:
298-
299- ``` php
300- $results = Categories::get();
301-
302- $results->linkNodes();
303- ```
304-
305- This will fill ` parent ` and ` children ` relations on every node so you can iterate over them without extra database
306- requests.
307-
308- To convert plain collection to tree:
309-
310- ```
311- $tree = $results->toTree();
312- ```
313-
314- ` $tree ` will contain only root nodes and to access children you can use ` children ` relation.
315-
316- ### Query builder extension
317-
318- This packages extends default query builder to introduce few helpful features.
319-
320266#### Including node depth
321267
322268If you need to know at which level the node is:
@@ -352,13 +298,26 @@ You can get nodes in reversed order:
352298$result = Category::reversed()->get();
353299```
354300
301+ ##### Shifting a node
302+
303+ To shift node up or down inside parent to affect default order:
304+
305+ ``` php
306+ $bool = $node->down();
307+ $bool = $node->up();
308+
309+ // Shift node by 3 siblings
310+ $bool = $node->down(3);
311+ ```
312+
313+ The result of the operation is boolean value of whether the node has changed its
314+ position.
315+
355316#### Constraints
356317
357318Various constraints that can be applied to the query builder:
358319
359320- __ whereIsRoot()__ to get only root nodes;
360- - __ hasChildren()__ to get nodes that have children;
361- - __ hasParent()__ to get non-root nodes;
362321- __ whereIsAfter($id)__ to get every node (not just siblings) that are after a node
363322 with specified id;
364323- __ whereIsBefore($id)__ to get every node that is before a node with specified id.
@@ -380,21 +339,88 @@ $result = Category::whereAncestorOf($node)->get();
380339
381340` $node ` can be either a primary key of the model or model instance.
382341
383- ### Node methods
342+ #### Building a tree
343+
344+ After getting a set of nodes, you can convert it to tree. For example:
345+
346+ ``` php
347+ $tree = Category::get()->toTree();
348+ ```
349+
350+ This will fill ` parent ` and ` children ` relationships on every node in the set and
351+ you can render a tree using recursive algorithm:
352+
353+ ``` php
354+ $nodes = Category::get()->toTree();
355+
356+ $traverse = function ($categories, $prefix = '-') use (& $traverse) {
357+ foreach ($categories as $category) {
358+ echo PHP_EOL.$prefix.' '.$category->name;
359+
360+ $traverse($category->children, $prefix.'-');
361+ }
362+ };
363+
364+ $traverse($nodes);
365+ ```
366+
367+ This will output something like this:
368+
369+ ```
370+ - Root
371+ -- Child 1
372+ --- Sub child 1
373+ -- Child 2
374+ - Another root
375+ ```
376+
377+ ##### Getting subtree
378+
379+ Sometimes you don't need whole tree to be loaded and just some subtree of specific node.
380+ It is show in following example:
381+
382+ ``` php
383+ $root = Category::find($rootId);
384+ $tree = $root->descendants->toTree($root);
385+ ```
386+
387+ Now ` $tree ` contains children of ` $root ` node.
388+
389+ If you don't need ` $root ` node itself, do following instead:
390+
391+ ``` php
392+ $tree = Category::descendantsOf($rootId)->toTree($rootId);
393+ ```
394+
395+ ### Deleting nodes
396+
397+ To delete a node:
398+
399+ ``` php
400+ $node->delete();
401+ ```
384402
385- Compute the number of descendants:
403+ ** IMPORTANT!** Any descendant that node has will also be deleted!
404+
405+ ** IMPORTANT!** Nodes are required to be deleted as models, ** don't** try do delete them using a query like so:
386406
387407``` php
388- $node->getDescendantCount ();
408+ Category::where('id', '=', $id)->delete ();
389409```
390410
411+ This will brake the tree!
412+
413+ ` SoftDeletes ` trait is supported, also on model level.
414+
415+ ### Helper methods
416+
391417To check if node is a descendant of other node:
392418
393419``` php
394420$bool = $node->isDescendantOf($parent);
395421```
396422
397- To check whether the node is root:
423+ To check whether the node is a root:
398424
399425``` php
400426$bool = $node->isRoot();
@@ -431,8 +457,8 @@ It will return an array with following keys:
431457
432458#### Fixing tree
433459
434- Since v3.1 tree can now be fixed. Using inheritance info from ` parent_id ` column, proper ` _lft ` and ` _rgt ` values
435- are set for every node.
460+ Since v3.1 tree can now be fixed. Using inheritance info from ` parent_id ` column,
461+ proper ` _lft ` and ` _rgt ` values are set for every node.
436462
437463``` php
438464Node::fixTree();
@@ -453,11 +479,36 @@ protected function getScopeAttributes()
453479}
454480```
455481
456- But now in order to execute some custom query for retrieving nodes , you need to
457- provide attributes that are used for scoping:
482+ But now in order to execute some custom query, you need to provide attributes
483+ that are used for scoping:
458484
459485``` php
460- MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get();
486+ MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OK
487+ MenuItem::descendantsOf($id)->get(); // WRONG: returns nodes from other scope
488+ MenuItem::scoped([ 'menu_id' => 5 ])->fixTree();
489+ ```
490+
491+ When requesting nodes using model instance, scopes applied automatically based
492+ on data of that model. See examples:
493+
494+ ``` php
495+ $node = MenuItem::findOrFail($id);
496+
497+ $node->siblings()->withDepth()->get(); // OK
498+ ```
499+
500+ To get scoped query builder using instance:
501+
502+ ``` php
503+ $node->newScopedQuery();
504+ ```
505+
506+ Note, that scoping is not required when retrieving model by primary key
507+ (since the key is unique):
508+
509+ ``` php
510+ $node = MenuItem::findOrFail($id); // OK
511+ $node = MenuItem::scoped([ 'menu_id' => 5 ])->findOrFail(); // OK, but redundant
461512```
462513
463514Requirements
@@ -466,9 +517,6 @@ Requirements
466517- PHP >= 5.4
467518- Laravel >= 4.1
468519
469- Models are extended from new base class rather than ` Eloquent ` , so it's not possible
470- to use another framework that overrides ` Eloquent ` .
471-
472520It is highly suggested to use database that supports transactions (like MySql's InnoDb)
473521to secure a tree from possible corruption.
474522
0 commit comments