A modern nested sets behavior for the Yii framework utilizing the Modified Preorder Tree Traversal algorithm.
$countries = new Menu(['name' => 'Countries']);
$countries->makeRoot();
$australia = new Menu(['name' => 'Australia']);
$australia->appendTo($countries);
The tree will look like this
- Countries
- Australia
$russia = new Menu(['name' => 'Russia']);
$russia->prependTo($countries);
The tree will look like this
- Countries
- Russia
- Australia
$newZeeland = new Menu(['name' => 'New Zeeland']);
$newZeeland->insertBefore($australia);
The tree will look like this
- Countries
- Russia
- New Zeeland
- Australia
$unitedStates = new Menu(['name' => 'United States']);
$unitedStates->insertAfter($australia);
The tree will look like this
- Countries
- Russia
- New Zeeland
- Australia
- United States
The preferred way to install this extension is through composer.
Either run
$ php composer.phar require creocoder/yii2-nested-sets:dev-master
or add
"creocoder/yii2-nested-sets": "dev-master"
to the require
section of your composer.json
file.
First you need to configure model as follows
use creocoder\nestedsets\NestedSetsBehavior;
class Tree extends \yii\db\ActiveRecord
{
public function behaviors() {
return [
NestedSetsBehavior::className(),
];
}
public function transactions()
{
return [
self::SCENARIO_DEFAULT => self::OP_ALL,
];
}
public static function find()
{
return new TreeQuery(get_called_class());
}
}
Second you need to configure query model as follows
use creocoder\nestedsets\NestedSetsQueryBehavior;
class TreeQuery extends \yii\db\ActiveQuery
{
public function behaviors() {
return [
NestedSetsQueryBehavior::className(),
];
}
}
The model has to have several fields. The fields that need to be set up are:
-
lft
, type integer. If you do not want to use this default name you can change it by setting up theleftAttribute
attribute. The example assumes that you want to change the name toleft
public function behaviors() { return [ 'nestedSets' => [ 'class' => NestedSetsBehavior::className(), 'leftAttribute' => 'left', ], ]; }
-
rgt
, type integer. To change the name of the field make the same setting as for lft, but use the attributerightAttribute
-
depth
, type integer. To change the name of the field make the same setting as for lft, but use the attributedepthAttribute
If you use a tree with multiple roots, besides the fields set up for the single tree, you have to set up an additional
field. The type of the new field is integer. The following example is for a tree attribute field named root
public function behaviors() {
return [
'nestedSets' => [
'class' => NestedSetsBehavior::className(),
'treeAttribute' => 'root',
],
];
}
This is an example migration to create a table for a model (with multiple roots) and all the fields necessary for the extension
$this->createTable('{{%menu}}', [
'id' => Schema::TYPE_PK,
'root' => Schema::TYPE_INTEGER,
'lft' => Schema::TYPE_INTEGER . ' NOT NULL',
'rgt' => Schema::TYPE_INTEGER . ' NOT NULL',
'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
//...
]);
If you are using a model with a single node you should remove the root field as it is unnecessary.
To get all the root nodes
$roots = Menu::find()->roots()->all();
foreach($roots as $root) {
echo $root->name;
}
To get all the leaves nodes
$leaves = Menu::find()->leaves()->all();
foreach($leaves as $leaf) {
echo $leaf->name;
}
To get all the leaves of a node
$countries = Menu::findOne(['name' => 'Countries']);
$leaves = $countries->leaves()->all();
foreach($leaves as $leaf) {
echo $leaf->name;
}
To get all the children of a node
$countries = Menu::findOne(['name' => 'Countries']);
$children = $countries->children()->all();
foreach($children as $child) {
echo $child->name;
}
To get the first level children of a node
$countries = Menu::findOne(['name' => 'Countries']);
$children = $countries->children(1)->all();
foreach($children as $child) {
echo $child->name;
}
To get all the parents of a node
$countries = Menu::findOne(['name' => 'Countries']);
$parents = $countries->parents()->all();
foreach($parents as $parent) {
echo $parent->name;
}
To get the first parent of a node
$countries = Menu::findOne(['name' => 'Countries']);
$parent = $countries->parents(1)->one();
echo $parent->name;