1
0
Fork 0
mirror of https://github.com/Oreolek/yii2-nested-sets.git synced 2024-07-01 05:55:00 +03:00
yii2-nested-sets/README.md

398 lines
8.1 KiB
Markdown
Raw Normal View History

2013-05-06 10:12:45 +03:00
Nested Set behavior 2 (preview version)
=======================================
2013-05-05 19:19:56 +03:00
2013-05-06 10:12:45 +03:00
This extension allows managing trees stored in database as nested sets.
It's implemented as Active Record behavior.
Installing and configuring
--------------------------
First you need to configure model as follows:
```php
class Category extends ActiveRecord
2013-05-06 10:12:45 +03:00
{
public function behaviors() {
return array(
'tree' => array(
'class' => 'NestedSet',
),
);
}
}
```
Second you need to configure query model as follows:
```php
class CategoryQuery extends ActiveQuery
{
public function behaviors() {
return array(
'tree' => array(
'class' => 'NestedSetQuery',
),
);
}
2013-05-06 10:12:45 +03:00
}
```
There is no need to validate fields specified in `leftAttribute`,
`rightAttribute`, `rootAttribute` and `levelAttribute` options. Moreover,
there could be problems if there are validation rules for these. Please
check if there are no rules for fields mentioned in model's rules() method.
In case of storing a single tree per database, DB structure can be built with
2013-05-06 11:27:26 +03:00
`schema/schema.sql`. If you're going to store multiple trees you'll need
`schema/schema_many_roots.sql`.
2013-05-06 10:12:45 +03:00
By default `leftAttribute`, `rightAttribute` and `levelAttribute` values are
matching field names in default DB schemas so you can skip configuring these.
There are two ways this behavior can work: one tree per table and multiple trees
per table. The mode is selected based on the value of `hasManyRoots` option that
is `false` by default meaning single tree mode. In multiple trees mode you can
set `rootAttribute` option to match existing field in the table storing the tree.
Selecting from a tree
---------------------
In the following we'll use an example model `Category` with the following in its
DB:
~~~
- 1. Mobile phones
- 2. iPhone
- 3. Samsung
- 4. X100
- 5. C200
- 6. Motorola
- 7. Cars
- 8. Audi
- 9. Ford
- 10. Mercedes
~~~
In this example we have two trees. Tree roots are ones with ID=1 and ID=7.
### Getting all roots
2013-05-06 10:57:43 +03:00
Using `NestedSet::roots()`:
2013-05-06 10:12:45 +03:00
```php
2013-05-06 10:57:43 +03:00
$roots = Category::find()->roots()->all();
2013-05-06 10:12:45 +03:00
```
Result:
Array of Active Record objects corresponding to Mobile phones and Cars nodes.
### Getting all descendants of a node
2013-05-06 10:57:43 +03:00
Using `NestedSet::descendants()`:
2013-05-06 10:12:45 +03:00
```php
2013-05-06 10:57:43 +03:00
$category = Category::find(1);
$descendants = $category->descendants()->all();
2013-05-06 10:12:45 +03:00
```
Result:
Array of Active Record objects corresponding to iPhone, Samsung, X100, C200 and Motorola.
### Getting all children of a node
2013-05-06 10:57:43 +03:00
Using `NestedSet::children()`:
2013-05-06 10:12:45 +03:00
```php
2013-05-06 10:57:43 +03:00
$category = Category::find(1);
$descendants = $category->children()->all();
2013-05-06 10:12:45 +03:00
```
Result:
Array of Active Record objects corresponding to iPhone, Samsung and Motorola.
### Getting all ancestors of a node
2013-05-06 10:57:43 +03:00
Using `NestedSet::ancestors()`:
2013-05-06 10:12:45 +03:00
```php
2013-05-06 10:57:43 +03:00
$category = Category::find(5);
$ancestors = $category->ancestors()->all();
2013-05-06 10:12:45 +03:00
```
Result:
Array of Active Record objects corresponding to Samsung and Mobile phones.
### Getting parent of a node
2013-05-06 10:57:43 +03:00
Using `NestedSet::parent()`:
2013-05-06 10:12:45 +03:00
```php
2013-05-06 10:57:43 +03:00
$category = Category::find(9);
2013-05-06 11:01:52 +03:00
$parent = $category->parent()->one();
2013-05-06 10:12:45 +03:00
```
Result:
Array of Active Record objects corresponding to Cars.
### Getting node siblings
2013-05-06 10:57:43 +03:00
Using `NestedSet::prev()` or
`NestedSet::next()`:
2013-05-06 10:12:45 +03:00
```php
2013-05-06 10:57:43 +03:00
$category = Category::find(9);
2013-05-06 11:01:52 +03:00
$nextSibling = $category->next()->one();
2013-05-06 10:12:45 +03:00
```
Result:
Array of Active Record objects corresponding to Mercedes.
### Getting the whole tree
You can get the whole tree using standard AR methods like the following.
For single tree per table:
```php
2013-05-06 18:21:58 +03:00
Category::find()->addOrderBy('lft')->all();
2013-05-06 10:12:45 +03:00
```
For multiple trees per table:
```php
2013-05-06 18:22:59 +03:00
Category::find()->where('root = ?', array($root_id))->addOrderBy('lft')->all();
2013-05-06 10:12:45 +03:00
```
Modifying a tree
----------------
In this section we'll build a tree like the one used in the previous section.
### Creating root nodes
2013-05-06 10:57:43 +03:00
You can create a root node using `NestedSet::saveNode()`.
2013-05-06 10:12:45 +03:00
In a single tree per table mode you can create only one root node. If you'll attempt
to create more there will be CException thrown.
```php
2013-05-06 10:57:43 +03:00
$root = new Category;
$root->title = 'Mobile Phones';
2013-05-06 10:12:45 +03:00
$root->saveNode();
2013-05-06 10:57:43 +03:00
$root = new Category;
$root->title = 'Cars';
2013-05-06 10:12:45 +03:00
$root->saveNode();
```
Result:
~~~
- 1. Mobile Phones
- 2. Cars
~~~
### Adding child nodes
There are multiple methods allowing you adding child nodes. To get more info
about these refer to API. Let's use these
to add nodes to the tree we have:
```php
2013-05-06 10:57:43 +03:00
$category1 = new Category;
$category1->title = 'Ford';
$category2 = new Category;
$category2->title = 'Mercedes';
$category3 = new Category;
$category3->title = 'Audi';
$root = Category::find(1);
2013-05-06 10:12:45 +03:00
$category1->appendTo($root);
$category2->insertAfter($category1);
$category3->insertBefore($category1);
```
Result:
~~~
- 1. Mobile phones
- 3. Audi
- 4. Ford
- 5. Mercedes
- 2. Cars
~~~
Logically the tree above doesn't looks correct. We'll fix it later.
```php
2013-05-06 10:57:43 +03:00
$category1 = new Category;
$category1->title = 'Samsung';
$category2 = new Category;
$category2->title = 'Motorola';
$category3 = new Category;
$category3->title = 'iPhone';
$root = Category::find(2);
2013-05-06 10:12:45 +03:00
$category1->appendTo($root);
$category2->insertAfter($category1);
$category3->prependTo($root);
```
Result:
~~~
- 1. Mobile phones
- 3. Audi
- 4. Ford
- 5. Mercedes
- 2. Cars
- 6. iPhone
- 7. Samsung
- 8. Motorola
~~~
```php
2013-05-06 10:57:43 +03:00
$category1 = new Category;
$category1->title = 'X100';
2013-05-06 10:59:30 +03:00
$category2 = new Category;
2013-05-06 10:57:43 +03:00
$category2->title = 'C200';
$node = Category::find(3);
2013-05-06 10:12:45 +03:00
$category1->appendTo($node);
$category2->prependTo($node);
```
Result:
~~~
- 1. Mobile phones
- 3. Audi
- 9. С200
- 10. X100
- 4. Ford
- 5. Mercedes
- 2. Cars
- 6. iPhone
- 7. Samsung
- 8. Motorola
~~~
Modifying a tree
----------------
In this section we'll finally make our tree logical.
### Tree modification methods
There are several methods allowing you to modify a tree. To get more info
about these refer to API.
Let's start:
```php
// move phones to the proper place
2013-05-06 10:57:43 +03:00
$x100 = Category::find(10);
$c200 = Category::find(9);
$samsung = Category::find(7);
2013-05-06 10:12:45 +03:00
$x100->moveAsFirst($samsung);
$c200->moveBefore($x100);
// now move all Samsung phones branch
2013-05-06 10:57:43 +03:00
$mobile_phones = Category::find(1);
2013-05-06 10:12:45 +03:00
$samsung->moveAsFirst($mobile_phones);
// move the rest of phone models
2013-05-06 10:57:43 +03:00
$iphone = Category::find(6);
2013-05-06 10:12:45 +03:00
$iphone->moveAsFirst($mobile_phones);
2013-05-06 10:57:43 +03:00
$motorola = Category::find(8);
2013-05-06 10:12:45 +03:00
$motorola->moveAfter($samsung);
// move car models to appropriate place
2013-05-06 10:57:43 +03:00
$cars = Category::find(2);
$audi = Category::find(3);
$ford = Category::find(4);
$mercedes = Category::find(5);
2013-05-06 10:12:45 +03:00
2013-05-06 10:57:43 +03:00
foreach(array($audi, $ford, $mercedes) as $category) {
2013-05-06 10:12:45 +03:00
$category->moveAsLast($cars);
2013-05-06 10:57:43 +03:00
}
2013-05-06 10:12:45 +03:00
```
Result:
~~~
- 1. Mobile phones
- 6. iPhone
- 7. Samsung
- 10. X100
- 9. С200
- 8. Motorola
- 2. Cars
- 3. Audi
- 4. Ford
- 5. Mercedes
~~~
### Moving a node making it a new root
There is a special `moveAsRoot()` method that allows moving a node and making it
a new root. All descendants are moved as well in this case.
Example:
```php
2013-05-06 10:57:43 +03:00
$node = Category::find(10);
2013-05-06 10:12:45 +03:00
$node->moveAsRoot();
```
### Identifying node type
There are three methods to get node type: `isRoot()`, `isLeaf()`, `isDescendantOf()`.
Example:
```php
2013-05-06 10:57:43 +03:00
$root = Category::find(1);
VarDumper::dump($root->isRoot()); //true;
VarDumper::dump($root->isLeaf()); //false;
$node = Category::find(9);
VarDumper::dump($node->isDescendantOf($root)); //true;
VarDumper::dump($node->isRoot()); //false;
VarDumper::dump($node->isLeaf()); //true;
$samsung = Category::find(7);
VarDumper::dump($node->isDescendantOf($samsung)); //true;
2013-05-06 10:12:45 +03:00
```
Useful code
------------
### Non-recursive tree traversal
```php
2013-05-06 18:09:34 +03:00
$categories = Category::find()->addOrderBy('lft')->all();
$level = 0;
foreach ($categories as $n => $category)
{
if ($category->level == $level) {
2013-11-13 05:55:56 +02:00
echo Html::endTag('li') . "\n";
2013-05-06 18:09:34 +03:00
} elseif ($category->level > $level) {
2013-11-13 05:55:56 +02:00
echo Html::beginTag('ul') . "\n";
2013-05-06 18:09:34 +03:00
} else {
2013-11-13 05:55:56 +02:00
echo Html::endTag('li') . "\n";
2013-05-06 18:09:34 +03:00
for ($i = $level - $category->level; $i; $i--) {
2013-11-13 05:55:56 +02:00
echo Html::endTag('ul') . "\n";
echo Html::endTag('li') . "\n";
2013-05-06 18:09:34 +03:00
}
}
2013-11-13 05:55:56 +02:00
echo Html::beginTag('li');
2013-05-06 18:09:34 +03:00
echo Html::encode($category->title);
$level = $category->level;
}
for ($i = $level; $i; $i--) {
2013-11-13 05:55:56 +02:00
echo Html::endTag('li') . "\n";
echo Html::endTag('ul') . "\n";
2013-05-06 18:09:34 +03:00
}
2013-05-06 10:12:45 +03:00
```