├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE.md
├── README.md
├── composer.json
├── composer.lock
├── phpunit.xml.dist
├── src
├── NestedSetsBehavior.php
└── NestedSetsQueryBehavior.php
└── tests
├── DatabaseTestCase.php
├── MysqlNestedSetsBehaviorTest.php
├── MysqlNestedSetsQueryBehaviorTest.php
├── NestedSetsBehaviorTest.php
├── NestedSetsQueryBehaviorTest.php
├── bootstrap.php
├── data
├── clean.xml
├── test-append-to-exists-another-tree.xml
├── test-append-to-exists-down.xml
├── test-append-to-exists-up.xml
├── test-append-to-new.xml
├── test-children-multiple-tree-with-depth.php
├── test-children-multiple-tree.php
├── test-children-with-depth.php
├── test-children.php
├── test-delete-with-children.xml
├── test-delete.xml
├── test-insert-after-exists-another-tree.xml
├── test-insert-after-exists-down.xml
├── test-insert-after-exists-up.xml
├── test-insert-after-new.xml
├── test-insert-before-exists-another-tree.xml
├── test-insert-before-exists-down.xml
├── test-insert-before-exists-up.xml
├── test-insert-before-new.xml
├── test-leaves-multiple-tree-query.php
├── test-leaves-multiple-tree.php
├── test-leaves-query.php
├── test-leaves.php
├── test-make-root-exists.xml
├── test-make-root-new.xml
├── test-next-multiple-tree.php
├── test-next.php
├── test-parents-multiple-tree-with-depth.php
├── test-parents-multiple-tree.php
├── test-parents-with-depth.php
├── test-parents.php
├── test-prepend-to-exists-another-tree.xml
├── test-prepend-to-exists-down.xml
├── test-prepend-to-exists-up.xml
├── test-prepend-to-new.xml
├── test-prev-multiple-tree.php
├── test-prev.php
├── test-roots-multiple-tree-query.php
├── test-roots-query.php
└── test.xml
├── migrations
├── mysql.sql
└── sqlite.sql
└── models
├── MultipleTree.php
├── MultipleTreeQuery.php
├── Tree.php
└── TreeQuery.php
/.gitignore:
--------------------------------------------------------------------------------
1 | # phpstorm project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # composer vendor dir
16 | /vendor
17 |
18 | # composer itself is not needed
19 | composer.phar
20 |
21 | # Mac DS_Store Files
22 | .DS_Store
23 |
24 | # phpunit itself is not needed
25 | phpunit.phar
26 | # local phpunit config
27 | /phpunit.xml
28 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | external_code_coverage:
3 | timeout: 1800
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 | - hhvm
8 |
9 | install:
10 | - composer self-update
11 | - composer global require fxp/composer-asset-plugin:~1.0
12 | - composer install
13 |
14 | before_script:
15 | - mysql -e 'create database yii2_nested_sets_test;'
16 |
17 | script:
18 | - vendor/bin/phpunit --coverage-clover=coverage.clover
19 |
20 | after_script:
21 | - wget https://scrutinizer-ci.com/ocular.phar
22 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The nested sets behavior for the Yii framework is free software.
2 | It is released under the terms of the following BSD License.
3 |
4 | Copyright © 2015, Alexander Kochetov (https://github.com/creocoder)
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions
9 | are met:
10 |
11 | * Redistributions of source code must retain the above copyright
12 | notice, this list of conditions and the following disclaimer.
13 | * Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in
15 | the documentation and/or other materials provided with the
16 | distribution.
17 | * Neither the name of Yii Software LLC nor the names of its
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nested Sets Behavior for Yii 2
2 |
3 | [](https://travis-ci.org/creocoder/yii2-nested-sets)
4 | [](https://scrutinizer-ci.com/g/creocoder/yii2-nested-sets/?branch=master)
5 | [](https://packagist.org/packages/creocoder/yii2-nested-sets)
6 | [](https://packagist.org/packages/creocoder/yii2-nested-sets)
7 |
8 | A modern nested sets behavior for the Yii framework utilizing the Modified Preorder Tree Traversal algorithm.
9 |
10 | ## Installation
11 |
12 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
13 |
14 | Either run
15 |
16 | ```bash
17 | $ composer require creocoder/yii2-nested-sets
18 | ```
19 |
20 | or add
21 |
22 | ```
23 | "creocoder/yii2-nested-sets": "0.9.*"
24 | ```
25 |
26 | to the `require` section of your `composer.json` file.
27 |
28 | ## Migrations
29 |
30 | Run the following command
31 |
32 | ```bash
33 | $ yii migrate/create create_menu_table
34 | ```
35 |
36 | Open the `/path/to/migrations/m_xxxxxx_xxxxxx_create_menu_table.php` file,
37 | inside the `up()` method add the following
38 |
39 | ```php
40 | $this->createTable('{{%menu}}', [
41 | 'id' => $this->primaryKey(),
42 | //'tree' => $this->integer()->notNull(),
43 | 'lft' => $this->integer()->notNull(),
44 | 'rgt' => $this->integer()->notNull(),
45 | 'depth' => $this->integer()->notNull(),
46 | 'name' => $this->string()->notNull(),
47 | ]);
48 | ```
49 |
50 | To use multiple tree mode uncomment `tree` field.
51 |
52 | ## Configuring
53 |
54 | Configure model as follows
55 |
56 | ```php
57 | use creocoder\nestedsets\NestedSetsBehavior;
58 |
59 | class Menu extends \yii\db\ActiveRecord
60 | {
61 | public function behaviors() {
62 | return [
63 | 'tree' => [
64 | 'class' => NestedSetsBehavior::className(),
65 | // 'treeAttribute' => 'tree',
66 | // 'leftAttribute' => 'lft',
67 | // 'rightAttribute' => 'rgt',
68 | // 'depthAttribute' => 'depth',
69 | ],
70 | ];
71 | }
72 |
73 | public function transactions()
74 | {
75 | return [
76 | self::SCENARIO_DEFAULT => self::OP_ALL,
77 | ];
78 | }
79 |
80 | public static function find()
81 | {
82 | return new MenuQuery(get_called_class());
83 | }
84 | }
85 | ```
86 |
87 | To use multiple tree mode uncomment `treeAttribute` array key inside `behaviors()` method.
88 |
89 | Configure query class as follows
90 |
91 | ```php
92 | use creocoder\nestedsets\NestedSetsQueryBehavior;
93 |
94 | class MenuQuery extends \yii\db\ActiveQuery
95 | {
96 | public function behaviors() {
97 | return [
98 | NestedSetsQueryBehavior::className(),
99 | ];
100 | }
101 | }
102 | ```
103 |
104 | ## Usage
105 |
106 | ### Making a root node
107 |
108 | To make a root node
109 |
110 | ```php
111 | $countries = new Menu(['name' => 'Countries']);
112 | $countries->makeRoot();
113 | ```
114 |
115 | The tree will look like this
116 |
117 | ```
118 | - Countries
119 | ```
120 |
121 | ### Prepending a node as the first child of another node
122 |
123 | To prepend a node as the first child of another node
124 |
125 | ```php
126 | $russia = new Menu(['name' => 'Russia']);
127 | $russia->prependTo($countries);
128 | ```
129 |
130 | The tree will look like this
131 |
132 | ```
133 | - Countries
134 | - Russia
135 | ```
136 |
137 | ### Appending a node as the last child of another node
138 |
139 | To append a node as the last child of another node
140 |
141 | ```php
142 | $australia = new Menu(['name' => 'Australia']);
143 | $australia->appendTo($countries);
144 | ```
145 |
146 | The tree will look like this
147 |
148 | ```
149 | - Countries
150 | - Russia
151 | - Australia
152 | ```
153 |
154 | ### Inserting a node before another node
155 |
156 | To insert a node before another node
157 |
158 | ```php
159 | $newZeeland = new Menu(['name' => 'New Zeeland']);
160 | $newZeeland->insertBefore($australia);
161 | ```
162 |
163 | The tree will look like this
164 |
165 | ```
166 | - Countries
167 | - Russia
168 | - New Zeeland
169 | - Australia
170 | ```
171 |
172 | ### Inserting a node after another node
173 |
174 | To insert a node after another node
175 |
176 | ```php
177 | $unitedStates = new Menu(['name' => 'United States']);
178 | $unitedStates->insertAfter($australia);
179 | ```
180 |
181 | The tree will look like this
182 | ```
183 | - Countries
184 | - Russia
185 | - New Zeeland
186 | - Australia
187 | - United States
188 | ```
189 |
190 | ### Getting the root nodes
191 |
192 | To get all the root nodes
193 |
194 | ```php
195 | $roots = Menu::find()->roots()->all();
196 | ```
197 |
198 | ### Getting the leaves nodes
199 |
200 | To get all the leaves nodes
201 |
202 | ```php
203 | $leaves = Menu::find()->leaves()->all();
204 | ```
205 |
206 | To get all the leaves of a node
207 |
208 | ```php
209 | $countries = Menu::findOne(['name' => 'Countries']);
210 | $leaves = $countries->leaves()->all();
211 | ```
212 |
213 | ### Getting children of a node
214 |
215 | To get all the children of a node
216 |
217 | ```php
218 | $countries = Menu::findOne(['name' => 'Countries']);
219 | $children = $countries->children()->all();
220 | ```
221 |
222 | To get the first level children of a node
223 |
224 | ```php
225 | $countries = Menu::findOne(['name' => 'Countries']);
226 | $children = $countries->children(1)->all();
227 | ```
228 |
229 | ### Getting parents of a node
230 |
231 | To get all the parents of a node
232 |
233 | ```php
234 | $countries = Menu::findOne(['name' => 'Countries']);
235 | $parents = $countries->parents()->all();
236 | ```
237 |
238 | To get the first parent of a node
239 |
240 | ```php
241 | $countries = Menu::findOne(['name' => 'Countries']);
242 | $parent = $countries->parents(1)->one();
243 | ```
244 |
245 | ## Donating
246 |
247 | Support this project and [others by creocoder](https://gratipay.com/creocoder/) via [gratipay](https://gratipay.com/creocoder/).
248 |
249 | [](https://gratipay.com/creocoder/)
250 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "creocoder/yii2-nested-sets",
3 | "description": "The nested sets behavior for the Yii framework",
4 | "keywords": [
5 | "yii2",
6 | "nested sets"
7 | ],
8 | "type": "yii2-extension",
9 | "license": "BSD-3-Clause",
10 | "authors": [
11 | {
12 | "name": "Alexander Kochetov",
13 | "email": "creocoder@gmail.com"
14 | }
15 | ],
16 | "require": {
17 | "yiisoft/yii2": "~2.0.0"
18 | },
19 | "require-dev": {
20 | "phpunit/phpunit": "~4.0",
21 | "phpunit/dbunit": "~1.0"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "creocoder\\nestedsets\\": "src"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 | src
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/NestedSetsBehavior.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class NestedSetsBehavior extends Behavior
24 | {
25 | const OPERATION_MAKE_ROOT = 'makeRoot';
26 | const OPERATION_PREPEND_TO = 'prependTo';
27 | const OPERATION_APPEND_TO = 'appendTo';
28 | const OPERATION_INSERT_BEFORE = 'insertBefore';
29 | const OPERATION_INSERT_AFTER = 'insertAfter';
30 | const OPERATION_DELETE_WITH_CHILDREN = 'deleteWithChildren';
31 |
32 | /**
33 | * @var string|false
34 | */
35 | public $treeAttribute = false;
36 | /**
37 | * @var string
38 | */
39 | public $leftAttribute = 'lft';
40 | /**
41 | * @var string
42 | */
43 | public $rightAttribute = 'rgt';
44 | /**
45 | * @var string
46 | */
47 | public $depthAttribute = 'depth';
48 | /**
49 | * @var string|null
50 | */
51 | protected $operation;
52 | /**
53 | * @var ActiveRecord|null
54 | */
55 | protected $node;
56 |
57 | /**
58 | * @inheritdoc
59 | */
60 | public function events()
61 | {
62 | return [
63 | ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert',
64 | ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
65 | ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
66 | ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
67 | ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
68 | ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
69 | ];
70 | }
71 |
72 | /**
73 | * Creates the root node if the active record is new or moves it
74 | * as the root node.
75 | * @param boolean $runValidation
76 | * @param array $attributes
77 | * @return boolean
78 | */
79 | public function makeRoot($runValidation = true, $attributes = null)
80 | {
81 | $this->operation = self::OPERATION_MAKE_ROOT;
82 |
83 | return $this->owner->save($runValidation, $attributes);
84 | }
85 |
86 | /**
87 | * Creates a node as the first child of the target node if the active
88 | * record is new or moves it as the first child of the target node.
89 | * @param ActiveRecord $node
90 | * @param boolean $runValidation
91 | * @param array $attributes
92 | * @return boolean
93 | */
94 | public function prependTo($node, $runValidation = true, $attributes = null)
95 | {
96 | $this->operation = self::OPERATION_PREPEND_TO;
97 | $this->node = $node;
98 |
99 | return $this->owner->save($runValidation, $attributes);
100 | }
101 |
102 | /**
103 | * Creates a node as the last child of the target node if the active
104 | * record is new or moves it as the last child of the target node.
105 | * @param ActiveRecord $node
106 | * @param boolean $runValidation
107 | * @param array $attributes
108 | * @return boolean
109 | */
110 | public function appendTo($node, $runValidation = true, $attributes = null)
111 | {
112 | $this->operation = self::OPERATION_APPEND_TO;
113 | $this->node = $node;
114 |
115 | return $this->owner->save($runValidation, $attributes);
116 | }
117 |
118 | /**
119 | * Creates a node as the previous sibling of the target node if the active
120 | * record is new or moves it as the previous sibling of the target node.
121 | * @param ActiveRecord $node
122 | * @param boolean $runValidation
123 | * @param array $attributes
124 | * @return boolean
125 | */
126 | public function insertBefore($node, $runValidation = true, $attributes = null)
127 | {
128 | $this->operation = self::OPERATION_INSERT_BEFORE;
129 | $this->node = $node;
130 |
131 | return $this->owner->save($runValidation, $attributes);
132 | }
133 |
134 | /**
135 | * Creates a node as the next sibling of the target node if the active
136 | * record is new or moves it as the next sibling of the target node.
137 | * @param ActiveRecord $node
138 | * @param boolean $runValidation
139 | * @param array $attributes
140 | * @return boolean
141 | */
142 | public function insertAfter($node, $runValidation = true, $attributes = null)
143 | {
144 | $this->operation = self::OPERATION_INSERT_AFTER;
145 | $this->node = $node;
146 |
147 | return $this->owner->save($runValidation, $attributes);
148 | }
149 |
150 | /**
151 | * Deletes a node and its children.
152 | * @return integer|false the number of rows deleted or false if
153 | * the deletion is unsuccessful for some reason.
154 | * @throws \Exception
155 | */
156 | public function deleteWithChildren()
157 | {
158 | $this->operation = self::OPERATION_DELETE_WITH_CHILDREN;
159 |
160 | if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
161 | return $this->deleteWithChildrenInternal();
162 | }
163 |
164 | $transaction = $this->owner->getDb()->beginTransaction();
165 |
166 | try {
167 | $result = $this->deleteWithChildrenInternal();
168 |
169 | if ($result === false) {
170 | $transaction->rollBack();
171 | } else {
172 | $transaction->commit();
173 | }
174 |
175 | return $result;
176 | } catch (\Exception $e) {
177 | $transaction->rollBack();
178 | throw $e;
179 | }
180 | }
181 |
182 | /**
183 | * @return integer|false the number of rows deleted or false if
184 | * the deletion is unsuccessful for some reason.
185 | */
186 | protected function deleteWithChildrenInternal()
187 | {
188 | if (!$this->owner->beforeDelete()) {
189 | return false;
190 | }
191 |
192 | $condition = [
193 | 'and',
194 | ['>=', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
195 | ['<=', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)]
196 | ];
197 |
198 | $this->applyTreeAttributeCondition($condition);
199 | $result = $this->owner->deleteAll($condition);
200 | $this->owner->setOldAttributes(null);
201 | $this->owner->afterDelete();
202 |
203 | return $result;
204 | }
205 |
206 | /**
207 | * Gets the parents of the node.
208 | * @param integer|null $depth the depth
209 | * @return \yii\db\ActiveQuery
210 | */
211 | public function parents($depth = null)
212 | {
213 | $condition = [
214 | 'and',
215 | ['<', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
216 | ['>', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)],
217 | ];
218 |
219 | if ($depth !== null) {
220 | $condition[] = ['>=', $this->depthAttribute, $this->owner->getAttribute($this->depthAttribute) - $depth];
221 | }
222 |
223 | $this->applyTreeAttributeCondition($condition);
224 |
225 | return $this->owner->find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);
226 | }
227 |
228 | /**
229 | * Gets the children of the node.
230 | * @param integer|null $depth the depth
231 | * @return \yii\db\ActiveQuery
232 | */
233 | public function children($depth = null)
234 | {
235 | $condition = [
236 | 'and',
237 | ['>', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
238 | ['<', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)],
239 | ];
240 |
241 | if ($depth !== null) {
242 | $condition[] = ['<=', $this->depthAttribute, $this->owner->getAttribute($this->depthAttribute) + $depth];
243 | }
244 |
245 | $this->applyTreeAttributeCondition($condition);
246 |
247 | return $this->owner->find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);
248 | }
249 |
250 | /**
251 | * Gets the leaves of the node.
252 | * @return \yii\db\ActiveQuery
253 | */
254 | public function leaves()
255 | {
256 | $condition = [
257 | 'and',
258 | ['>', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
259 | ['<', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)],
260 | [$this->rightAttribute => new Expression($this->owner->getDb()->quoteColumnName($this->leftAttribute) . '+ 1')],
261 | ];
262 |
263 | $this->applyTreeAttributeCondition($condition);
264 |
265 | return $this->owner->find()->andWhere($condition)->addOrderBy([$this->leftAttribute => SORT_ASC]);
266 | }
267 |
268 | /**
269 | * Gets the previous sibling of the node.
270 | * @return \yii\db\ActiveQuery
271 | */
272 | public function prev()
273 | {
274 | $condition = [$this->rightAttribute => $this->owner->getAttribute($this->leftAttribute) - 1];
275 | $this->applyTreeAttributeCondition($condition);
276 |
277 | return $this->owner->find()->andWhere($condition);
278 | }
279 |
280 | /**
281 | * Gets the next sibling of the node.
282 | * @return \yii\db\ActiveQuery
283 | */
284 | public function next()
285 | {
286 | $condition = [$this->leftAttribute => $this->owner->getAttribute($this->rightAttribute) + 1];
287 | $this->applyTreeAttributeCondition($condition);
288 |
289 | return $this->owner->find()->andWhere($condition);
290 | }
291 |
292 | /**
293 | * Determines whether the node is root.
294 | * @return boolean whether the node is root
295 | */
296 | public function isRoot()
297 | {
298 | return $this->owner->getAttribute($this->leftAttribute) == 1;
299 | }
300 |
301 | /**
302 | * Determines whether the node is child of the parent node.
303 | * @param ActiveRecord $node the parent node
304 | * @return boolean whether the node is child of the parent node
305 | */
306 | public function isChildOf($node)
307 | {
308 | $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute)
309 | && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute);
310 |
311 | if ($result && $this->treeAttribute !== false) {
312 | $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
313 | }
314 |
315 | return $result;
316 | }
317 |
318 | /**
319 | * Determines whether the node is leaf.
320 | * @return boolean whether the node is leaf
321 | */
322 | public function isLeaf()
323 | {
324 | return $this->owner->getAttribute($this->rightAttribute) - $this->owner->getAttribute($this->leftAttribute) === 1;
325 | }
326 |
327 | /**
328 | * @throws NotSupportedException
329 | */
330 | public function beforeInsert()
331 | {
332 | if ($this->node !== null && !$this->node->getIsNewRecord()) {
333 | $this->node->refresh();
334 | }
335 |
336 | switch ($this->operation) {
337 | case self::OPERATION_MAKE_ROOT:
338 | $this->beforeInsertRootNode();
339 | break;
340 | case self::OPERATION_PREPEND_TO:
341 | $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
342 | break;
343 | case self::OPERATION_APPEND_TO:
344 | $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute), 1);
345 | break;
346 | case self::OPERATION_INSERT_BEFORE:
347 | $this->beforeInsertNode($this->node->getAttribute($this->leftAttribute), 0);
348 | break;
349 | case self::OPERATION_INSERT_AFTER:
350 | $this->beforeInsertNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
351 | break;
352 | default:
353 | throw new NotSupportedException('Method "'. get_class($this->owner) . '::insert" is not supported for inserting new nodes.');
354 | }
355 | }
356 |
357 | /**
358 | * @throws Exception
359 | */
360 | protected function beforeInsertRootNode()
361 | {
362 | if ($this->treeAttribute === false && $this->owner->find()->roots()->exists()) {
363 | throw new Exception('Can not create more than one root when "treeAttribute" is false.');
364 | }
365 |
366 | $this->owner->setAttribute($this->leftAttribute, 1);
367 | $this->owner->setAttribute($this->rightAttribute, 2);
368 | $this->owner->setAttribute($this->depthAttribute, 0);
369 | }
370 |
371 | /**
372 | * @param integer $value
373 | * @param integer $depth
374 | * @throws Exception
375 | */
376 | protected function beforeInsertNode($value, $depth)
377 | {
378 | if ($this->node->getIsNewRecord()) {
379 | throw new Exception('Can not create a node when the target node is new record.');
380 | }
381 |
382 | if ($depth === 0 && $this->node->isRoot()) {
383 | throw new Exception('Can not create a node when the target node is root.');
384 | }
385 |
386 | $this->owner->setAttribute($this->leftAttribute, $value);
387 | $this->owner->setAttribute($this->rightAttribute, $value + 1);
388 | $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + $depth);
389 |
390 | if ($this->treeAttribute !== false) {
391 | $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
392 | }
393 |
394 | $this->shiftLeftRightAttribute($value, 2);
395 | }
396 |
397 | /**
398 | * @throws Exception
399 | */
400 | public function afterInsert()
401 | {
402 | if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== false) {
403 | $this->owner->setAttribute($this->treeAttribute, $this->owner->getPrimaryKey());
404 | $primaryKey = $this->owner->primaryKey();
405 |
406 | if (!isset($primaryKey[0])) {
407 | throw new Exception('"' . get_class($this->owner) . '" must have a primary key.');
408 | }
409 |
410 | $this->owner->updateAll(
411 | [$this->treeAttribute => $this->owner->getAttribute($this->treeAttribute)],
412 | [$primaryKey[0] => $this->owner->getAttribute($this->treeAttribute)]
413 | );
414 | }
415 |
416 | $this->operation = null;
417 | $this->node = null;
418 | }
419 |
420 | /**
421 | * @throws Exception
422 | */
423 | public function beforeUpdate()
424 | {
425 | if ($this->node !== null && !$this->node->getIsNewRecord()) {
426 | $this->node->refresh();
427 | }
428 |
429 | switch ($this->operation) {
430 | case self::OPERATION_MAKE_ROOT:
431 | if ($this->treeAttribute === false) {
432 | throw new Exception('Can not move a node as the root when "treeAttribute" is false.');
433 | }
434 |
435 | if ($this->owner->isRoot()) {
436 | throw new Exception('Can not move the root node as the root.');
437 | }
438 |
439 | break;
440 | case self::OPERATION_INSERT_BEFORE:
441 | case self::OPERATION_INSERT_AFTER:
442 | if ($this->node->isRoot()) {
443 | throw new Exception('Can not move a node when the target node is root.');
444 | }
445 | case self::OPERATION_PREPEND_TO:
446 | case self::OPERATION_APPEND_TO:
447 | if ($this->node->getIsNewRecord()) {
448 | throw new Exception('Can not move a node when the target node is new record.');
449 | }
450 |
451 | if ($this->owner->equals($this->node)) {
452 | throw new Exception('Can not move a node when the target node is same.');
453 | }
454 |
455 | if ($this->node->isChildOf($this->owner)) {
456 | throw new Exception('Can not move a node when the target node is child.');
457 | }
458 | }
459 | }
460 |
461 | /**
462 | * @return void
463 | */
464 | public function afterUpdate()
465 | {
466 | switch ($this->operation) {
467 | case self::OPERATION_MAKE_ROOT:
468 | $this->moveNodeAsRoot();
469 | break;
470 | case self::OPERATION_PREPEND_TO:
471 | $this->moveNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
472 | break;
473 | case self::OPERATION_APPEND_TO:
474 | $this->moveNode($this->node->getAttribute($this->rightAttribute), 1);
475 | break;
476 | case self::OPERATION_INSERT_BEFORE:
477 | $this->moveNode($this->node->getAttribute($this->leftAttribute), 0);
478 | break;
479 | case self::OPERATION_INSERT_AFTER:
480 | $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
481 | break;
482 | default:
483 | return;
484 | }
485 |
486 | $this->operation = null;
487 | $this->node = null;
488 | }
489 |
490 | /**
491 | * @return void
492 | */
493 | protected function moveNodeAsRoot()
494 | {
495 | $db = $this->owner->getDb();
496 | $leftValue = $this->owner->getAttribute($this->leftAttribute);
497 | $rightValue = $this->owner->getAttribute($this->rightAttribute);
498 | $depthValue = $this->owner->getAttribute($this->depthAttribute);
499 | $treeValue = $this->owner->getAttribute($this->treeAttribute);
500 | $leftAttribute = $db->quoteColumnName($this->leftAttribute);
501 | $rightAttribute = $db->quoteColumnName($this->rightAttribute);
502 | $depthAttribute = $db->quoteColumnName($this->depthAttribute);
503 |
504 | $this->owner->updateAll(
505 | [
506 | $this->leftAttribute => new Expression($leftAttribute . sprintf('%+d', 1 - $leftValue)),
507 | $this->rightAttribute => new Expression($rightAttribute . sprintf('%+d', 1 - $leftValue)),
508 | $this->depthAttribute => new Expression($depthAttribute . sprintf('%+d', -$depthValue)),
509 | $this->treeAttribute => $this->owner->getPrimaryKey(),
510 | ],
511 | [
512 | 'and',
513 | ['>=', $this->leftAttribute, $leftValue],
514 | ['<=', $this->rightAttribute, $rightValue],
515 | [$this->treeAttribute => $treeValue]
516 | ]
517 | );
518 |
519 | $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1);
520 | }
521 |
522 | /**
523 | * @param integer $value
524 | * @param integer $depth
525 | */
526 | protected function moveNode($value, $depth)
527 | {
528 | $db = $this->owner->getDb();
529 | $leftValue = $this->owner->getAttribute($this->leftAttribute);
530 | $rightValue = $this->owner->getAttribute($this->rightAttribute);
531 | $depthValue = $this->owner->getAttribute($this->depthAttribute);
532 | $depthAttribute = $db->quoteColumnName($this->depthAttribute);
533 | $depth = $this->node->getAttribute($this->depthAttribute) - $depthValue + $depth;
534 |
535 | if ($this->treeAttribute === false
536 | || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
537 | $delta = $rightValue - $leftValue + 1;
538 | $this->shiftLeftRightAttribute($value, $delta);
539 |
540 | if ($leftValue >= $value) {
541 | $leftValue += $delta;
542 | $rightValue += $delta;
543 | }
544 |
545 | $condition = ['and', ['>=', $this->leftAttribute, $leftValue], ['<=', $this->rightAttribute, $rightValue]];
546 | $this->applyTreeAttributeCondition($condition);
547 |
548 | $this->owner->updateAll(
549 | [$this->depthAttribute => new Expression($depthAttribute . sprintf('%+d', $depth))],
550 | $condition
551 | );
552 |
553 | foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
554 | $condition = ['and', ['>=', $attribute, $leftValue], ['<=', $attribute, $rightValue]];
555 | $this->applyTreeAttributeCondition($condition);
556 |
557 | $this->owner->updateAll(
558 | [$attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $value - $leftValue))],
559 | $condition
560 | );
561 | }
562 |
563 | $this->shiftLeftRightAttribute($rightValue + 1, -$delta);
564 | } else {
565 | $leftAttribute = $db->quoteColumnName($this->leftAttribute);
566 | $rightAttribute = $db->quoteColumnName($this->rightAttribute);
567 | $nodeRootValue = $this->node->getAttribute($this->treeAttribute);
568 |
569 | foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
570 | $this->owner->updateAll(
571 | [$attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $rightValue - $leftValue + 1))],
572 | ['and', ['>=', $attribute, $value], [$this->treeAttribute => $nodeRootValue]]
573 | );
574 | }
575 |
576 | $delta = $value - $leftValue;
577 |
578 | $this->owner->updateAll(
579 | [
580 | $this->leftAttribute => new Expression($leftAttribute . sprintf('%+d', $delta)),
581 | $this->rightAttribute => new Expression($rightAttribute . sprintf('%+d', $delta)),
582 | $this->depthAttribute => new Expression($depthAttribute . sprintf('%+d', $depth)),
583 | $this->treeAttribute => $nodeRootValue,
584 | ],
585 | [
586 | 'and',
587 | ['>=', $this->leftAttribute, $leftValue],
588 | ['<=', $this->rightAttribute, $rightValue],
589 | [$this->treeAttribute => $this->owner->getAttribute($this->treeAttribute)],
590 | ]
591 | );
592 |
593 | $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1);
594 | }
595 | }
596 |
597 | /**
598 | * @throws Exception
599 | * @throws NotSupportedException
600 | */
601 | public function beforeDelete()
602 | {
603 | if ($this->owner->getIsNewRecord()) {
604 | throw new Exception('Can not delete a node when it is new record.');
605 | }
606 |
607 | if ($this->owner->isRoot() && $this->operation !== self::OPERATION_DELETE_WITH_CHILDREN) {
608 | throw new NotSupportedException('Method "'. get_class($this->owner) . '::delete" is not supported for deleting root nodes.');
609 | }
610 |
611 | $this->owner->refresh();
612 | }
613 |
614 | /**
615 | * @return void
616 | */
617 | public function afterDelete()
618 | {
619 | $leftValue = $this->owner->getAttribute($this->leftAttribute);
620 | $rightValue = $this->owner->getAttribute($this->rightAttribute);
621 |
622 | if ($this->owner->isLeaf() || $this->operation === self::OPERATION_DELETE_WITH_CHILDREN) {
623 | $this->shiftLeftRightAttribute($rightValue + 1, $leftValue - $rightValue - 1);
624 | } else {
625 | $condition = [
626 | 'and',
627 | ['>=', $this->leftAttribute, $this->owner->getAttribute($this->leftAttribute)],
628 | ['<=', $this->rightAttribute, $this->owner->getAttribute($this->rightAttribute)]
629 | ];
630 |
631 | $this->applyTreeAttributeCondition($condition);
632 | $db = $this->owner->getDb();
633 |
634 | $this->owner->updateAll(
635 | [
636 | $this->leftAttribute => new Expression($db->quoteColumnName($this->leftAttribute) . sprintf('%+d', -1)),
637 | $this->rightAttribute => new Expression($db->quoteColumnName($this->rightAttribute) . sprintf('%+d', -1)),
638 | $this->depthAttribute => new Expression($db->quoteColumnName($this->depthAttribute) . sprintf('%+d', -1)),
639 | ],
640 | $condition
641 | );
642 |
643 | $this->shiftLeftRightAttribute($rightValue + 1, -2);
644 | }
645 |
646 | $this->operation = null;
647 | $this->node = null;
648 | }
649 |
650 | /**
651 | * @param integer $value
652 | * @param integer $delta
653 | */
654 | protected function shiftLeftRightAttribute($value, $delta)
655 | {
656 | $db = $this->owner->getDb();
657 |
658 | foreach ([$this->leftAttribute, $this->rightAttribute] as $attribute) {
659 | $condition = ['>=', $attribute, $value];
660 | $this->applyTreeAttributeCondition($condition);
661 |
662 | $this->owner->updateAll(
663 | [$attribute => new Expression($db->quoteColumnName($attribute) . sprintf('%+d', $delta))],
664 | $condition
665 | );
666 | }
667 | }
668 |
669 | /**
670 | * @param array $condition
671 | */
672 | protected function applyTreeAttributeCondition(&$condition)
673 | {
674 | if ($this->treeAttribute !== false) {
675 | $condition = [
676 | 'and',
677 | $condition,
678 | [$this->treeAttribute => $this->owner->getAttribute($this->treeAttribute)]
679 | ];
680 | }
681 |
682 | }
683 | }
684 |
--------------------------------------------------------------------------------
/src/NestedSetsQueryBehavior.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class NestedSetsQueryBehavior extends Behavior
21 | {
22 | /**
23 | * Gets the root nodes.
24 | * @return \yii\db\ActiveQuery the owner
25 | */
26 | public function roots()
27 | {
28 | $model = new $this->owner->modelClass();
29 |
30 | $this->owner
31 | ->andWhere([$model->leftAttribute => 1])
32 | ->addOrderBy([$model->primaryKey()[0] => SORT_ASC]);
33 |
34 | return $this->owner;
35 | }
36 |
37 | /**
38 | * Gets the leaf nodes.
39 | * @return \yii\db\ActiveQuery the owner
40 | */
41 | public function leaves()
42 | {
43 | $model = new $this->owner->modelClass();
44 | $db = $model->getDb();
45 |
46 | $columns = [$model->leftAttribute => SORT_ASC];
47 |
48 | if ($model->treeAttribute !== false) {
49 | $columns = [$model->treeAttribute => SORT_ASC] + $columns;
50 | }
51 |
52 | $this->owner
53 | ->andWhere([$model->rightAttribute => new Expression($db->quoteColumnName($model->leftAttribute) . '+ 1')])
54 | ->addOrderBy($columns);
55 |
56 | return $this->owner;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/DatabaseTestCase.php:
--------------------------------------------------------------------------------
1 | createDefaultDBConnection(Yii::$app->getDb()->pdo);
23 | }
24 |
25 | /**
26 | * @inheritdoc
27 | */
28 | public function getDataSet()
29 | {
30 | return $this->createFlatXMLDataSet(__DIR__ . '/data/test.xml');
31 | }
32 |
33 | /**
34 | * @inheritdoc
35 | */
36 | protected function setUp()
37 | {
38 | if (Yii::$app->get('db', false) === null) {
39 | $this->markTestSkipped();
40 | } else {
41 | parent::setUp();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/MysqlNestedSetsBehaviorTest.php:
--------------------------------------------------------------------------------
1 | set('db', [
25 | 'class' => Connection::className(),
26 | 'dsn' => 'mysql:host=localhost;dbname=yii2_nested_sets_test',
27 | 'username' => 'root',
28 | 'password' => '',
29 | ]);
30 |
31 | Yii::$app->getDb()->open();
32 | $lines = explode(';', file_get_contents(__DIR__ . '/migrations/mysql.sql'));
33 |
34 | foreach ($lines as $line) {
35 | if (trim($line) !== '') {
36 | Yii::$app->getDb()->pdo->exec($line);
37 | }
38 | }
39 | } catch (\Exception $e) {
40 | Yii::$app->clear('db');
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/MysqlNestedSetsQueryBehaviorTest.php:
--------------------------------------------------------------------------------
1 | set('db', [
25 | 'class' => Connection::className(),
26 | 'dsn' => 'mysql:host=localhost;dbname=yii2_nested_sets_test',
27 | 'username' => 'root',
28 | 'password' => '',
29 | ]);
30 |
31 | Yii::$app->getDb()->open();
32 | $lines = explode(';', file_get_contents(__DIR__ . '/migrations/mysql.sql'));
33 |
34 | foreach ($lines as $line) {
35 | if (trim($line) !== '') {
36 | Yii::$app->getDb()->pdo->exec($line);
37 | }
38 | }
39 | } catch (\Exception $e) {
40 | Yii::$app->clear('db');
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/NestedSetsBehaviorTest.php:
--------------------------------------------------------------------------------
1 | createFlatXMLDataSet(__DIR__ . '/data/clean.xml');
24 | $this->getDatabaseTester()->setDataSet($dataSet);
25 | $this->getDatabaseTester()->onSetUp();
26 |
27 | $node = new Tree(['name' => 'Root']);
28 | $this->assertTrue($node->makeRoot());
29 |
30 | $node = new MultipleTree(['name' => 'Root 1']);
31 | $this->assertTrue($node->makeRoot());
32 |
33 | $node = new MultipleTree(['name' => 'Root 2']);
34 | $this->assertTrue($node->makeRoot());
35 |
36 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
37 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-make-root-new.xml');
38 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
39 | }
40 |
41 | /**
42 | * @expectedException \yii\db\Exception
43 | */
44 | public function testMakeRootNewExceptionIsRaisedWhenTreeAttributeIsFalseAndRootIsExists()
45 | {
46 | $node = new Tree(['name' => 'Root']);
47 | $node->makeRoot();
48 | }
49 |
50 | public function testPrependToNew()
51 | {
52 | $node = new Tree(['name' => 'New node']);
53 | $this->assertTrue($node->prependTo(Tree::findOne(9)));
54 |
55 | $node = new MultipleTree(['name' => 'New node']);
56 | $this->assertTrue($node->prependTo(MultipleTree::findOne(31)));
57 |
58 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
59 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-prepend-to-new.xml');
60 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
61 | }
62 |
63 | /**
64 | * @expectedException \yii\db\Exception
65 | */
66 | public function testPrependToNewExceptionIsRaisedWhenTargetIsNewRecord()
67 | {
68 | $node = new Tree(['name' => 'New node']);
69 | $node->prependTo(new Tree());
70 | }
71 |
72 | public function testAppendToNew()
73 | {
74 | $node = new Tree(['name' => 'New node']);
75 | $this->assertTrue($node->appendTo(Tree::findOne(9)));
76 |
77 | $node = new MultipleTree(['name' => 'New node']);
78 | $this->assertTrue($node->appendTo(MultipleTree::findOne(31)));
79 |
80 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
81 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-append-to-new.xml');
82 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
83 | }
84 |
85 | /**
86 | * @expectedException \yii\db\Exception
87 | */
88 | public function testAppendNewToExceptionIsRaisedWhenTargetIsNewRecord()
89 | {
90 | $node = new Tree(['name' => 'New node']);
91 | $node->appendTo(new Tree());
92 | }
93 |
94 | public function testInsertBeforeNew()
95 | {
96 | $node = new Tree(['name' => 'New node']);
97 | $this->assertTrue($node->insertBefore(Tree::findOne(9)));
98 |
99 | $node = new MultipleTree(['name' => 'New node']);
100 | $this->assertTrue($node->insertBefore(MultipleTree::findOne(31)));
101 |
102 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
103 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-before-new.xml');
104 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
105 | }
106 |
107 | /**
108 | * @expectedException \yii\db\Exception
109 | */
110 | public function testInsertBeforeNewExceptionIsRaisedWhenTargetIsNewRecord()
111 | {
112 | $node = new Tree(['name' => 'New node']);
113 | $node->insertBefore(new Tree());
114 | }
115 |
116 | /**
117 | * @expectedException \yii\db\Exception
118 | */
119 | public function testInsertBeforeNewExceptionIsRaisedWhenTargetIsRoot()
120 | {
121 | $node = new Tree(['name' => 'New node']);
122 | $node->insertBefore(Tree::findOne(1));
123 | }
124 |
125 | public function testInsertAfterNew()
126 | {
127 | $node = new Tree(['name' => 'New node']);
128 | $this->assertTrue($node->insertAfter(Tree::findOne(9)));
129 |
130 | $node = new MultipleTree(['name' => 'New node']);
131 | $this->assertTrue($node->insertAfter(MultipleTree::findOne(31)));
132 |
133 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
134 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-after-new.xml');
135 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
136 | }
137 |
138 | /**
139 | * @expectedException \yii\db\Exception
140 | */
141 | public function testInsertAfterNewExceptionIsRaisedWhenTargetIsNewRecord()
142 | {
143 | $node = new Tree(['name' => 'New node']);
144 | $node->insertAfter(new Tree());
145 | }
146 |
147 | /**
148 | * @expectedException \yii\db\Exception
149 | */
150 | public function testInsertAfterNewExceptionIsRaisedWhenTargetIsRoot()
151 | {
152 | $node = new Tree(['name' => 'New node']);
153 | $node->insertAfter(Tree::findOne(1));
154 | }
155 |
156 | public function testMakeRootExists()
157 | {
158 | $node = MultipleTree::findOne(31);
159 | $node->name = 'Updated node 2';
160 | $this->assertTrue($node->makeRoot());
161 |
162 | $dataSet = $this->getConnection()->createDataSet(['multiple_tree']);
163 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-make-root-exists.xml');
164 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
165 | }
166 |
167 | /**
168 | * @expectedException \yii\db\Exception
169 | */
170 | public function testMakeRootExistsExceptionIsRaisedWhenTreeAttributeIsFalse()
171 | {
172 | $node = Tree::findOne(9);
173 | $node->makeRoot();
174 | }
175 |
176 | /**
177 | * @expectedException \yii\db\Exception
178 | */
179 | public function testMakeRootExistsExceptionIsRaisedWhenItsRoot()
180 | {
181 | $node = MultipleTree::findOne(23);
182 | $node->makeRoot();
183 | }
184 |
185 | public function testPrependToExistsUp()
186 | {
187 | $node = Tree::findOne(9);
188 | $node->name = 'Updated node 2';
189 | $this->assertTrue($node->prependTo(Tree::findOne(2)));
190 |
191 | $node = MultipleTree::findOne(31);
192 | $node->name = 'Updated node 2';
193 | $this->assertTrue($node->prependTo(MultipleTree::findOne(24)));
194 |
195 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
196 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-prepend-to-exists-up.xml');
197 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
198 | }
199 |
200 | public function testPrependToExistsDown()
201 | {
202 | $node = Tree::findOne(9);
203 | $node->name = 'Updated node 2';
204 | $this->assertTrue($node->prependTo(Tree::findOne(16)));
205 |
206 | $node = MultipleTree::findOne(31);
207 | $node->name = 'Updated node 2';
208 | $this->assertTrue($node->prependTo(MultipleTree::findOne(38)));
209 |
210 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
211 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-prepend-to-exists-down.xml');
212 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
213 | }
214 |
215 | public function testPrependToExistsAnotherTree()
216 | {
217 | $node = MultipleTree::findOne(9);
218 | $node->name = 'Updated node 2';
219 | $this->assertTrue($node->prependTo(MultipleTree::findOne(53)));
220 |
221 | $dataSet = $this->getConnection()->createDataSet(['multiple_tree']);
222 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-prepend-to-exists-another-tree.xml');
223 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
224 | }
225 |
226 | /**
227 | * @expectedException \yii\db\Exception
228 | */
229 | public function testPrependToExistsExceptionIsRaisedWhenTargetIsNewRecord()
230 | {
231 | $node = Tree::findOne(9);
232 | $node->prependTo(new Tree());
233 | }
234 |
235 | /**
236 | * @expectedException \yii\db\Exception
237 | */
238 | public function testPrependToExistsExceptionIsRaisedWhenTargetIsSame()
239 | {
240 | $node = Tree::findOne(9);
241 | $node->prependTo(Tree::findOne(9));
242 | }
243 |
244 | /**
245 | * @expectedException \yii\db\Exception
246 | */
247 | public function testPrependToExistsExceptionIsRaisedWhenTargetIsChild()
248 | {
249 | $node = Tree::findOne(9);
250 | $node->prependTo(Tree::findOne(11));
251 | }
252 |
253 | public function testAppendToExistsUp()
254 | {
255 | $node = Tree::findOne(9);
256 | $node->name = 'Updated node 2';
257 | $this->assertTrue($node->appendTo(Tree::findOne(2)));
258 |
259 | $node = MultipleTree::findOne(31);
260 | $node->name = 'Updated node 2';
261 | $this->assertTrue($node->appendTo(MultipleTree::findOne(24)));
262 |
263 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
264 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-append-to-exists-up.xml');
265 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
266 | }
267 |
268 | public function testAppendToExistsDown()
269 | {
270 | $node = Tree::findOne(9);
271 | $node->name = 'Updated node 2';
272 | $this->assertTrue($node->appendTo(Tree::findOne(16)));
273 |
274 | $node = MultipleTree::findOne(31);
275 | $node->name = 'Updated node 2';
276 | $this->assertTrue($node->appendTo(MultipleTree::findOne(38)));
277 |
278 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
279 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-append-to-exists-down.xml');
280 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
281 | }
282 |
283 | public function testAppendToExistsAnotherTree()
284 | {
285 | $node = MultipleTree::findOne(9);
286 | $node->name = 'Updated node 2';
287 | $this->assertTrue($node->appendTo(MultipleTree::findOne(53)));
288 |
289 | $dataSet = $this->getConnection()->createDataSet(['multiple_tree']);
290 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-append-to-exists-another-tree.xml');
291 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
292 | }
293 |
294 | /**
295 | * @expectedException \yii\db\Exception
296 | */
297 | public function testAppendToExistsExceptionIsRaisedWhenTargetIsNewRecord()
298 | {
299 | $node = Tree::findOne(9);
300 | $node->appendTo(new Tree());
301 | }
302 |
303 | /**
304 | * @expectedException \yii\db\Exception
305 | */
306 | public function testAppendToExistsExceptionIsRaisedWhenTargetIsSame()
307 | {
308 | $node = Tree::findOne(9);
309 | $node->appendTo(Tree::findOne(9));
310 | }
311 |
312 | /**
313 | * @expectedException \yii\db\Exception
314 | */
315 | public function testAppendToExistsExceptionIsRaisedWhenTargetIsChild()
316 | {
317 | $node = Tree::findOne(9);
318 | $node->appendTo(Tree::findOne(11));
319 | }
320 |
321 | public function testInsertBeforeExistsUp()
322 | {
323 | $node = Tree::findOne(9);
324 | $node->name = 'Updated node 2';
325 | $this->assertTrue($node->insertBefore(Tree::findOne(2)));
326 |
327 | $node = MultipleTree::findOne(31);
328 | $node->name = 'Updated node 2';
329 | $this->assertTrue($node->insertBefore(MultipleTree::findOne(24)));
330 |
331 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
332 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-before-exists-up.xml');
333 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
334 | }
335 |
336 | public function testInsertBeforeExistsDown()
337 | {
338 | $node = Tree::findOne(9);
339 | $node->name = 'Updated node 2';
340 | $this->assertTrue($node->insertBefore(Tree::findOne(16)));
341 |
342 | $node = MultipleTree::findOne(31);
343 | $node->name = 'Updated node 2';
344 | $this->assertTrue($node->insertBefore(MultipleTree::findOne(38)));
345 |
346 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
347 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-before-exists-down.xml');
348 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
349 | }
350 |
351 | public function testInsertBeforeExistsAnotherTree()
352 | {
353 | $node = MultipleTree::findOne(9);
354 | $node->name = 'Updated node 2';
355 | $this->assertTrue($node->insertBefore(MultipleTree::findOne(53)));
356 |
357 | $dataSet = $this->getConnection()->createDataSet(['multiple_tree']);
358 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-before-exists-another-tree.xml');
359 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
360 | }
361 |
362 | /**
363 | * @expectedException \yii\db\Exception
364 | */
365 | public function testInsertBeforeExistsExceptionIsRaisedWhenTargetIsNewRecord()
366 | {
367 | $node = Tree::findOne(9);
368 | $node->insertBefore(new Tree());
369 | }
370 |
371 | /**
372 | * @expectedException \yii\db\Exception
373 | */
374 | public function testInsertBeforeExistsExceptionIsRaisedWhenTargetIsSame()
375 | {
376 | $node = Tree::findOne(9);
377 | $node->insertBefore(Tree::findOne(9));
378 | }
379 |
380 | /**
381 | * @expectedException \yii\db\Exception
382 | */
383 | public function testInsertBeforeExistsExceptionIsRaisedWhenTargetIsChild()
384 | {
385 | $node = Tree::findOne(9);
386 | $node->insertBefore(Tree::findOne(11));
387 | }
388 |
389 | /**
390 | * @expectedException \yii\db\Exception
391 | */
392 | public function testInsertBeforeExistsExceptionIsRaisedWhenTargetIsRoot()
393 | {
394 | $node = Tree::findOne(9);
395 | $node->insertBefore(Tree::findOne(1));
396 | }
397 |
398 | public function testInsertAfterExistsUp()
399 | {
400 | $node = Tree::findOne(9);
401 | $node->name = 'Updated node 2';
402 | $this->assertTrue($node->insertAfter(Tree::findOne(2)));
403 |
404 | $node = MultipleTree::findOne(31);
405 | $node->name = 'Updated node 2';
406 | $this->assertTrue($node->insertAfter(MultipleTree::findOne(24)));
407 |
408 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
409 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-after-exists-up.xml');
410 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
411 | }
412 |
413 | public function testInsertAfterExistsDown()
414 | {
415 | $node = Tree::findOne(9);
416 | $node->name = 'Updated node 2';
417 | $this->assertTrue($node->insertAfter(Tree::findOne(16)));
418 |
419 | $node = MultipleTree::findOne(31);
420 | $node->name = 'Updated node 2';
421 | $this->assertTrue($node->insertAfter(MultipleTree::findOne(38)));
422 |
423 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
424 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-after-exists-down.xml');
425 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
426 | }
427 |
428 | public function testInsertAfterExistsAnotherTree()
429 | {
430 | $node = MultipleTree::findOne(9);
431 | $node->name = 'Updated node 2';
432 | $this->assertTrue($node->insertAfter(MultipleTree::findOne(53)));
433 |
434 | $dataSet = $this->getConnection()->createDataSet(['multiple_tree']);
435 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-insert-after-exists-another-tree.xml');
436 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
437 | }
438 |
439 | /**
440 | * @expectedException \yii\db\Exception
441 | */
442 | public function testInsertAfterExistsExceptionIsRaisedWhenTargetIsNewRecord()
443 | {
444 | $node = Tree::findOne(9);
445 | $node->insertAfter(new Tree());
446 | }
447 |
448 | /**
449 | * @expectedException \yii\db\Exception
450 | */
451 | public function testInsertAfterExistsExceptionIsRaisedWhenTargetIsSame()
452 | {
453 | $node = Tree::findOne(9);
454 | $node->insertAfter(Tree::findOne(9));
455 | }
456 |
457 | /**
458 | * @expectedException \yii\db\Exception
459 | */
460 | public function testInsertAfterExistsExceptionIsRaisedWhenTargetIsChild()
461 | {
462 | $node = Tree::findOne(9);
463 | $node->insertAfter(Tree::findOne(11));
464 | }
465 |
466 | /**
467 | * @expectedException \yii\db\Exception
468 | */
469 | public function testInsertAfterExistsExceptionIsRaisedWhenTargetIsRoot()
470 | {
471 | $node = Tree::findOne(9);
472 | $node->insertAfter(Tree::findOne(1));
473 | }
474 |
475 | public function testDeleteWithChildren()
476 | {
477 | $this->assertEquals(7, Tree::findOne(9)->deleteWithChildren());
478 | $this->assertEquals(7, MultipleTree::findOne(31)->deleteWithChildren());
479 |
480 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
481 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-delete-with-children.xml');
482 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
483 | }
484 |
485 | /**
486 | * @expectedException \yii\db\Exception
487 | */
488 | public function testDeleteWithChildrenExceptionIsRaisedWhenNodeIsNewRecord()
489 | {
490 | $node = new Tree();
491 | $node->deleteWithChildren();
492 | }
493 |
494 | public function testDelete()
495 | {
496 | $this->assertEquals(1, Tree::findOne(9)->delete());
497 | $this->assertEquals(1, MultipleTree::findOne(31)->delete());
498 |
499 | $dataSet = $this->getConnection()->createDataSet(['tree', 'multiple_tree']);
500 | $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-delete.xml');
501 | $this->assertDataSetsEqual($expectedDataSet, $dataSet);
502 | }
503 |
504 | /**
505 | * @expectedException \yii\db\Exception
506 | */
507 | public function testDeleteExceptionIsRaisedWhenNodeIsNewRecord()
508 | {
509 | $node = new Tree();
510 | $node->delete();
511 | }
512 |
513 | /**
514 | * @expectedException \yii\base\NotSupportedException
515 | */
516 | public function testDeleteExceptionIsRaisedWhenNodeIsRoot()
517 | {
518 | $node = Tree::findOne(1);
519 | $node->delete();
520 | }
521 |
522 | /**
523 | * @expectedException \yii\base\NotSupportedException
524 | */
525 | public function testExceptionIsRaisedWhenInsertIsCalled()
526 | {
527 | $node = new Tree(['name' => 'Node']);
528 | $node->insert();
529 | }
530 |
531 | public function testUpdate()
532 | {
533 | $node = Tree::findOne(9);
534 | $node->name = 'Updated node 2';
535 | $this->assertEquals(1, $node->update());
536 | }
537 |
538 | public function testParents()
539 | {
540 | $this->assertEquals(
541 | require(__DIR__ . '/data/test-parents.php'),
542 | ArrayHelper::toArray(Tree::findOne(11)->parents()->all())
543 | );
544 |
545 | $this->assertEquals(
546 | require(__DIR__ . '/data/test-parents-multiple-tree.php'),
547 | ArrayHelper::toArray(MultipleTree::findOne(33)->parents()->all())
548 | );
549 |
550 | $this->assertEquals(
551 | require(__DIR__ . '/data/test-parents-with-depth.php'),
552 | ArrayHelper::toArray(Tree::findOne(11)->parents(1)->all())
553 | );
554 |
555 | $this->assertEquals(
556 | require(__DIR__ . '/data/test-parents-multiple-tree-with-depth.php'),
557 | ArrayHelper::toArray(MultipleTree::findOne(33)->parents(1)->all())
558 | );
559 | }
560 |
561 | public function testChildren()
562 | {
563 | $this->assertEquals(
564 | require(__DIR__ . '/data/test-children.php'),
565 | ArrayHelper::toArray(Tree::findOne(9)->children()->all())
566 | );
567 |
568 | $this->assertEquals(
569 | require(__DIR__ . '/data/test-children-multiple-tree.php'),
570 | ArrayHelper::toArray(MultipleTree::findOne(31)->children()->all())
571 | );
572 |
573 | $this->assertEquals(
574 | require(__DIR__ . '/data/test-children-with-depth.php'),
575 | ArrayHelper::toArray(Tree::findOne(9)->children(1)->all())
576 | );
577 |
578 | $this->assertEquals(
579 | require(__DIR__ . '/data/test-children-multiple-tree-with-depth.php'),
580 | ArrayHelper::toArray(MultipleTree::findOne(31)->children(1)->all())
581 | );
582 | }
583 |
584 | public function testLeaves()
585 | {
586 | $this->assertEquals(
587 | require(__DIR__ . '/data/test-leaves.php'),
588 | ArrayHelper::toArray(Tree::findOne(9)->leaves()->all())
589 | );
590 |
591 | $this->assertEquals(
592 | require(__DIR__ . '/data/test-leaves-multiple-tree.php'),
593 | ArrayHelper::toArray(MultipleTree::findOne(31)->leaves()->all())
594 | );
595 | }
596 |
597 | public function testPrev()
598 | {
599 | $this->assertEquals(
600 | require(__DIR__ . '/data/test-prev.php'),
601 | ArrayHelper::toArray(Tree::findOne(9)->prev()->all())
602 | );
603 |
604 | $this->assertEquals(
605 | require(__DIR__ . '/data/test-prev-multiple-tree.php'),
606 | ArrayHelper::toArray(MultipleTree::findOne(31)->prev()->all())
607 | );
608 | }
609 |
610 | public function testNext()
611 | {
612 | $this->assertEquals(
613 | require(__DIR__ . '/data/test-next.php'),
614 | ArrayHelper::toArray(Tree::findOne(9)->next()->all())
615 | );
616 |
617 | $this->assertEquals(
618 | require(__DIR__ . '/data/test-next-multiple-tree.php'),
619 | ArrayHelper::toArray(MultipleTree::findOne(31)->next()->all())
620 | );
621 | }
622 |
623 | public function testIsRoot()
624 | {
625 | $this->assertTrue(Tree::findOne(1)->isRoot());
626 | $this->assertFalse(Tree::findOne(2)->isRoot());
627 | }
628 |
629 | public function testIsChildOf()
630 | {
631 | $node = MultipleTree::findOne(26);
632 | $this->assertTrue($node->isChildOf(MultipleTree::findOne(25)));
633 | $this->assertTrue($node->isChildOf(MultipleTree::findOne(23)));
634 | $this->assertFalse($node->isChildOf(MultipleTree::findOne(3)));
635 | $this->assertFalse($node->isChildOf(MultipleTree::findOne(1)));
636 | }
637 |
638 | public function testIsLeaf()
639 | {
640 | $this->assertTrue(Tree::findOne(4)->isLeaf());
641 | $this->assertFalse(Tree::findOne(1)->isLeaf());
642 | }
643 |
644 | /**
645 | * @inheritdoc
646 | */
647 | public static function setUpBeforeClass()
648 | {
649 | try {
650 | Yii::$app->set('db', [
651 | 'class' => Connection::className(),
652 | 'dsn' => 'sqlite::memory:',
653 | ]);
654 |
655 | Yii::$app->getDb()->open();
656 | $lines = explode(';', file_get_contents(__DIR__ . '/migrations/sqlite.sql'));
657 |
658 | foreach ($lines as $line) {
659 | if (trim($line) !== '') {
660 | Yii::$app->getDb()->pdo->exec($line);
661 | }
662 | }
663 | } catch (\Exception $e) {
664 | Yii::$app->clear('db');
665 | }
666 | }
667 | }
668 |
--------------------------------------------------------------------------------
/tests/NestedSetsQueryBehaviorTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(
24 | require(__DIR__ . '/data/test-roots-query.php'),
25 | ArrayHelper::toArray(Tree::find()->roots()->all())
26 | );
27 |
28 | $this->assertEquals(
29 | require(__DIR__ . '/data/test-roots-multiple-tree-query.php'),
30 | ArrayHelper::toArray(MultipleTree::find()->roots()->all())
31 | );
32 | }
33 |
34 | public function testLeaves()
35 | {
36 | $this->assertEquals(
37 | require(__DIR__ . '/data/test-leaves-query.php'),
38 | ArrayHelper::toArray(Tree::find()->leaves()->all())
39 | );
40 |
41 | $this->assertEquals(
42 | require(__DIR__ . '/data/test-leaves-multiple-tree-query.php'),
43 | ArrayHelper::toArray(MultipleTree::find()->leaves()->all())
44 | );
45 | }
46 |
47 | /**
48 | * @inheritdoc
49 | */
50 | public static function setUpBeforeClass()
51 | {
52 | try {
53 | Yii::$app->set('db', [
54 | 'class' => Connection::className(),
55 | 'dsn' => 'sqlite::memory:',
56 | ]);
57 |
58 | Yii::$app->getDb()->open();
59 | $lines = explode(';', file_get_contents(__DIR__ . '/migrations/sqlite.sql'));
60 |
61 | foreach ($lines as $line) {
62 | if (trim($line) !== '') {
63 | Yii::$app->getDb()->pdo->exec($line);
64 | }
65 | }
66 | } catch (\Exception $e) {
67 | Yii::$app->clear('db');
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | 'unit',
13 | 'basePath' => __DIR__,
14 | ]);
15 |
--------------------------------------------------------------------------------
/tests/data/clean.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/data/test-append-to-exists-another-tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/data/test-append-to-exists-down.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-append-to-exists-up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-append-to-new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/tests/data/test-children-multiple-tree-with-depth.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 32,
7 | 'tree' => 23,
8 | 'lft' => 17,
9 | 'rgt' => 22,
10 | 'depth' => 2,
11 | 'name' => 'Node 2.1',
12 | ),
13 | 1 =>
14 | array (
15 | 'id' => 35,
16 | 'tree' => 23,
17 | 'lft' => 23,
18 | 'rgt' => 28,
19 | 'depth' => 2,
20 | 'name' => 'Node 2.2',
21 | ),
22 | );
--------------------------------------------------------------------------------
/tests/data/test-children-multiple-tree.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 32,
7 | 'tree' => 23,
8 | 'lft' => 17,
9 | 'rgt' => 22,
10 | 'depth' => 2,
11 | 'name' => 'Node 2.1',
12 | ),
13 | 1 =>
14 | array (
15 | 'id' => 33,
16 | 'tree' => 23,
17 | 'lft' => 18,
18 | 'rgt' => 19,
19 | 'depth' => 3,
20 | 'name' => 'Node 2.1.1',
21 | ),
22 | 2 =>
23 | array (
24 | 'id' => 34,
25 | 'tree' => 23,
26 | 'lft' => 20,
27 | 'rgt' => 21,
28 | 'depth' => 3,
29 | 'name' => 'Node 2.1.2',
30 | ),
31 | 3 =>
32 | array (
33 | 'id' => 35,
34 | 'tree' => 23,
35 | 'lft' => 23,
36 | 'rgt' => 28,
37 | 'depth' => 2,
38 | 'name' => 'Node 2.2',
39 | ),
40 | 4 =>
41 | array (
42 | 'id' => 36,
43 | 'tree' => 23,
44 | 'lft' => 24,
45 | 'rgt' => 25,
46 | 'depth' => 3,
47 | 'name' => 'Node 2.2.1',
48 | ),
49 | 5 =>
50 | array (
51 | 'id' => 37,
52 | 'tree' => 23,
53 | 'lft' => 26,
54 | 'rgt' => 27,
55 | 'depth' => 3,
56 | 'name' => 'Node 2.2.2',
57 | ),
58 | );
--------------------------------------------------------------------------------
/tests/data/test-children-with-depth.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 10,
7 | 'lft' => 17,
8 | 'rgt' => 22,
9 | 'depth' => 2,
10 | 'name' => 'Node 2.1',
11 | ),
12 | 1 =>
13 | array (
14 | 'id' => 13,
15 | 'lft' => 23,
16 | 'rgt' => 28,
17 | 'depth' => 2,
18 | 'name' => 'Node 2.2',
19 | ),
20 | );
--------------------------------------------------------------------------------
/tests/data/test-children.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 10,
7 | 'lft' => 17,
8 | 'rgt' => 22,
9 | 'depth' => 2,
10 | 'name' => 'Node 2.1',
11 | ),
12 | 1 =>
13 | array (
14 | 'id' => 11,
15 | 'lft' => 18,
16 | 'rgt' => 19,
17 | 'depth' => 3,
18 | 'name' => 'Node 2.1.1',
19 | ),
20 | 2 =>
21 | array (
22 | 'id' => 12,
23 | 'lft' => 20,
24 | 'rgt' => 21,
25 | 'depth' => 3,
26 | 'name' => 'Node 2.1.2',
27 | ),
28 | 3 =>
29 | array (
30 | 'id' => 13,
31 | 'lft' => 23,
32 | 'rgt' => 28,
33 | 'depth' => 2,
34 | 'name' => 'Node 2.2',
35 | ),
36 | 4 =>
37 | array (
38 | 'id' => 14,
39 | 'lft' => 24,
40 | 'rgt' => 25,
41 | 'depth' => 3,
42 | 'name' => 'Node 2.2.1',
43 | ),
44 | 5 =>
45 | array (
46 | 'id' => 15,
47 | 'lft' => 26,
48 | 'rgt' => 27,
49 | 'depth' => 3,
50 | 'name' => 'Node 2.2.2',
51 | ),
52 | );
--------------------------------------------------------------------------------
/tests/data/test-delete-with-children.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/tests/data/test-delete.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/tests/data/test-insert-after-exists-another-tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/data/test-insert-after-exists-down.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-insert-after-exists-up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-insert-after-new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/tests/data/test-insert-before-exists-another-tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/data/test-insert-before-exists-down.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-insert-before-exists-up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-insert-before-new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/tests/data/test-leaves-multiple-tree-query.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 4,
7 | 'tree' => 1,
8 | 'lft' => 4,
9 | 'rgt' => 5,
10 | 'depth' => 3,
11 | 'name' => 'Node 1.1.1',
12 | ),
13 | 1 =>
14 | array (
15 | 'id' => 5,
16 | 'tree' => 1,
17 | 'lft' => 6,
18 | 'rgt' => 7,
19 | 'depth' => 3,
20 | 'name' => 'Node 1.1.2',
21 | ),
22 | 2 =>
23 | array (
24 | 'id' => 7,
25 | 'tree' => 1,
26 | 'lft' => 10,
27 | 'rgt' => 11,
28 | 'depth' => 3,
29 | 'name' => 'Node 1.2.1',
30 | ),
31 | 3 =>
32 | array (
33 | 'id' => 8,
34 | 'tree' => 1,
35 | 'lft' => 12,
36 | 'rgt' => 13,
37 | 'depth' => 3,
38 | 'name' => 'Node 1.2.2',
39 | ),
40 | 4 =>
41 | array (
42 | 'id' => 11,
43 | 'tree' => 1,
44 | 'lft' => 18,
45 | 'rgt' => 19,
46 | 'depth' => 3,
47 | 'name' => 'Node 2.1.1',
48 | ),
49 | 5 =>
50 | array (
51 | 'id' => 12,
52 | 'tree' => 1,
53 | 'lft' => 20,
54 | 'rgt' => 21,
55 | 'depth' => 3,
56 | 'name' => 'Node 2.1.2',
57 | ),
58 | 6 =>
59 | array (
60 | 'id' => 14,
61 | 'tree' => 1,
62 | 'lft' => 24,
63 | 'rgt' => 25,
64 | 'depth' => 3,
65 | 'name' => 'Node 2.2.1',
66 | ),
67 | 7 =>
68 | array (
69 | 'id' => 15,
70 | 'tree' => 1,
71 | 'lft' => 26,
72 | 'rgt' => 27,
73 | 'depth' => 3,
74 | 'name' => 'Node 2.2.2',
75 | ),
76 | 8 =>
77 | array (
78 | 'id' => 18,
79 | 'tree' => 1,
80 | 'lft' => 32,
81 | 'rgt' => 33,
82 | 'depth' => 3,
83 | 'name' => 'Node 3.1.1',
84 | ),
85 | 9 =>
86 | array (
87 | 'id' => 19,
88 | 'tree' => 1,
89 | 'lft' => 34,
90 | 'rgt' => 35,
91 | 'depth' => 3,
92 | 'name' => 'Node 3.1.2',
93 | ),
94 | 10 =>
95 | array (
96 | 'id' => 21,
97 | 'tree' => 1,
98 | 'lft' => 38,
99 | 'rgt' => 39,
100 | 'depth' => 3,
101 | 'name' => 'Node 3.2.1',
102 | ),
103 | 11 =>
104 | array (
105 | 'id' => 22,
106 | 'tree' => 1,
107 | 'lft' => 40,
108 | 'rgt' => 41,
109 | 'depth' => 3,
110 | 'name' => 'Node 3.2.2',
111 | ),
112 | 12 =>
113 | array (
114 | 'id' => 26,
115 | 'tree' => 23,
116 | 'lft' => 4,
117 | 'rgt' => 5,
118 | 'depth' => 3,
119 | 'name' => 'Node 1.1.1',
120 | ),
121 | 13 =>
122 | array (
123 | 'id' => 27,
124 | 'tree' => 23,
125 | 'lft' => 6,
126 | 'rgt' => 7,
127 | 'depth' => 3,
128 | 'name' => 'Node 1.1.2',
129 | ),
130 | 14 =>
131 | array (
132 | 'id' => 29,
133 | 'tree' => 23,
134 | 'lft' => 10,
135 | 'rgt' => 11,
136 | 'depth' => 3,
137 | 'name' => 'Node 1.2.1',
138 | ),
139 | 15 =>
140 | array (
141 | 'id' => 30,
142 | 'tree' => 23,
143 | 'lft' => 12,
144 | 'rgt' => 13,
145 | 'depth' => 3,
146 | 'name' => 'Node 1.2.2',
147 | ),
148 | 16 =>
149 | array (
150 | 'id' => 33,
151 | 'tree' => 23,
152 | 'lft' => 18,
153 | 'rgt' => 19,
154 | 'depth' => 3,
155 | 'name' => 'Node 2.1.1',
156 | ),
157 | 17 =>
158 | array (
159 | 'id' => 34,
160 | 'tree' => 23,
161 | 'lft' => 20,
162 | 'rgt' => 21,
163 | 'depth' => 3,
164 | 'name' => 'Node 2.1.2',
165 | ),
166 | 18 =>
167 | array (
168 | 'id' => 36,
169 | 'tree' => 23,
170 | 'lft' => 24,
171 | 'rgt' => 25,
172 | 'depth' => 3,
173 | 'name' => 'Node 2.2.1',
174 | ),
175 | 19 =>
176 | array (
177 | 'id' => 37,
178 | 'tree' => 23,
179 | 'lft' => 26,
180 | 'rgt' => 27,
181 | 'depth' => 3,
182 | 'name' => 'Node 2.2.2',
183 | ),
184 | 20 =>
185 | array (
186 | 'id' => 40,
187 | 'tree' => 23,
188 | 'lft' => 32,
189 | 'rgt' => 33,
190 | 'depth' => 3,
191 | 'name' => 'Node 3.1.1',
192 | ),
193 | 21 =>
194 | array (
195 | 'id' => 41,
196 | 'tree' => 23,
197 | 'lft' => 34,
198 | 'rgt' => 35,
199 | 'depth' => 3,
200 | 'name' => 'Node 3.1.2',
201 | ),
202 | 22 =>
203 | array (
204 | 'id' => 43,
205 | 'tree' => 23,
206 | 'lft' => 38,
207 | 'rgt' => 39,
208 | 'depth' => 3,
209 | 'name' => 'Node 3.2.1',
210 | ),
211 | 23 =>
212 | array (
213 | 'id' => 44,
214 | 'tree' => 23,
215 | 'lft' => 40,
216 | 'rgt' => 41,
217 | 'depth' => 3,
218 | 'name' => 'Node 3.2.2',
219 | ),
220 | 24 =>
221 | array (
222 | 'id' => 48,
223 | 'tree' => 45,
224 | 'lft' => 4,
225 | 'rgt' => 5,
226 | 'depth' => 3,
227 | 'name' => 'Node 1.1.1',
228 | ),
229 | 25 =>
230 | array (
231 | 'id' => 49,
232 | 'tree' => 45,
233 | 'lft' => 6,
234 | 'rgt' => 7,
235 | 'depth' => 3,
236 | 'name' => 'Node 1.1.2',
237 | ),
238 | 26 =>
239 | array (
240 | 'id' => 51,
241 | 'tree' => 45,
242 | 'lft' => 10,
243 | 'rgt' => 11,
244 | 'depth' => 3,
245 | 'name' => 'Node 1.2.1',
246 | ),
247 | 27 =>
248 | array (
249 | 'id' => 52,
250 | 'tree' => 45,
251 | 'lft' => 12,
252 | 'rgt' => 13,
253 | 'depth' => 3,
254 | 'name' => 'Node 1.2.2',
255 | ),
256 | 28 =>
257 | array (
258 | 'id' => 55,
259 | 'tree' => 45,
260 | 'lft' => 18,
261 | 'rgt' => 19,
262 | 'depth' => 3,
263 | 'name' => 'Node 2.1.1',
264 | ),
265 | 29 =>
266 | array (
267 | 'id' => 56,
268 | 'tree' => 45,
269 | 'lft' => 20,
270 | 'rgt' => 21,
271 | 'depth' => 3,
272 | 'name' => 'Node 2.1.2',
273 | ),
274 | 30 =>
275 | array (
276 | 'id' => 58,
277 | 'tree' => 45,
278 | 'lft' => 24,
279 | 'rgt' => 25,
280 | 'depth' => 3,
281 | 'name' => 'Node 2.2.1',
282 | ),
283 | 31 =>
284 | array (
285 | 'id' => 59,
286 | 'tree' => 45,
287 | 'lft' => 26,
288 | 'rgt' => 27,
289 | 'depth' => 3,
290 | 'name' => 'Node 2.2.2',
291 | ),
292 | 32 =>
293 | array (
294 | 'id' => 62,
295 | 'tree' => 45,
296 | 'lft' => 32,
297 | 'rgt' => 33,
298 | 'depth' => 3,
299 | 'name' => 'Node 3.1.1',
300 | ),
301 | 33 =>
302 | array (
303 | 'id' => 63,
304 | 'tree' => 45,
305 | 'lft' => 34,
306 | 'rgt' => 35,
307 | 'depth' => 3,
308 | 'name' => 'Node 3.1.2',
309 | ),
310 | 34 =>
311 | array (
312 | 'id' => 65,
313 | 'tree' => 45,
314 | 'lft' => 38,
315 | 'rgt' => 39,
316 | 'depth' => 3,
317 | 'name' => 'Node 3.2.1',
318 | ),
319 | 35 =>
320 | array (
321 | 'id' => 66,
322 | 'tree' => 45,
323 | 'lft' => 40,
324 | 'rgt' => 41,
325 | 'depth' => 3,
326 | 'name' => 'Node 3.2.2',
327 | ),
328 | );
--------------------------------------------------------------------------------
/tests/data/test-leaves-multiple-tree.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 33,
7 | 'tree' => 23,
8 | 'lft' => 18,
9 | 'rgt' => 19,
10 | 'depth' => 3,
11 | 'name' => 'Node 2.1.1',
12 | ),
13 | 1 =>
14 | array (
15 | 'id' => 34,
16 | 'tree' => 23,
17 | 'lft' => 20,
18 | 'rgt' => 21,
19 | 'depth' => 3,
20 | 'name' => 'Node 2.1.2',
21 | ),
22 | 2 =>
23 | array (
24 | 'id' => 36,
25 | 'tree' => 23,
26 | 'lft' => 24,
27 | 'rgt' => 25,
28 | 'depth' => 3,
29 | 'name' => 'Node 2.2.1',
30 | ),
31 | 3 =>
32 | array (
33 | 'id' => 37,
34 | 'tree' => 23,
35 | 'lft' => 26,
36 | 'rgt' => 27,
37 | 'depth' => 3,
38 | 'name' => 'Node 2.2.2',
39 | ),
40 | );
--------------------------------------------------------------------------------
/tests/data/test-leaves-query.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 4,
7 | 'lft' => 4,
8 | 'rgt' => 5,
9 | 'depth' => 3,
10 | 'name' => 'Node 1.1.1',
11 | ),
12 | 1 =>
13 | array (
14 | 'id' => 5,
15 | 'lft' => 6,
16 | 'rgt' => 7,
17 | 'depth' => 3,
18 | 'name' => 'Node 1.1.2',
19 | ),
20 | 2 =>
21 | array (
22 | 'id' => 7,
23 | 'lft' => 10,
24 | 'rgt' => 11,
25 | 'depth' => 3,
26 | 'name' => 'Node 1.2.1',
27 | ),
28 | 3 =>
29 | array (
30 | 'id' => 8,
31 | 'lft' => 12,
32 | 'rgt' => 13,
33 | 'depth' => 3,
34 | 'name' => 'Node 1.2.2',
35 | ),
36 | 4 =>
37 | array (
38 | 'id' => 11,
39 | 'lft' => 18,
40 | 'rgt' => 19,
41 | 'depth' => 3,
42 | 'name' => 'Node 2.1.1',
43 | ),
44 | 5 =>
45 | array (
46 | 'id' => 12,
47 | 'lft' => 20,
48 | 'rgt' => 21,
49 | 'depth' => 3,
50 | 'name' => 'Node 2.1.2',
51 | ),
52 | 6 =>
53 | array (
54 | 'id' => 14,
55 | 'lft' => 24,
56 | 'rgt' => 25,
57 | 'depth' => 3,
58 | 'name' => 'Node 2.2.1',
59 | ),
60 | 7 =>
61 | array (
62 | 'id' => 15,
63 | 'lft' => 26,
64 | 'rgt' => 27,
65 | 'depth' => 3,
66 | 'name' => 'Node 2.2.2',
67 | ),
68 | 8 =>
69 | array (
70 | 'id' => 18,
71 | 'lft' => 32,
72 | 'rgt' => 33,
73 | 'depth' => 3,
74 | 'name' => 'Node 3.1.1',
75 | ),
76 | 9 =>
77 | array (
78 | 'id' => 19,
79 | 'lft' => 34,
80 | 'rgt' => 35,
81 | 'depth' => 3,
82 | 'name' => 'Node 3.1.2',
83 | ),
84 | 10 =>
85 | array (
86 | 'id' => 21,
87 | 'lft' => 38,
88 | 'rgt' => 39,
89 | 'depth' => 3,
90 | 'name' => 'Node 3.2.1',
91 | ),
92 | 11 =>
93 | array (
94 | 'id' => 22,
95 | 'lft' => 40,
96 | 'rgt' => 41,
97 | 'depth' => 3,
98 | 'name' => 'Node 3.2.2',
99 | ),
100 | );
--------------------------------------------------------------------------------
/tests/data/test-leaves.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 11,
7 | 'lft' => 18,
8 | 'rgt' => 19,
9 | 'depth' => 3,
10 | 'name' => 'Node 2.1.1',
11 | ),
12 | 1 =>
13 | array (
14 | 'id' => 12,
15 | 'lft' => 20,
16 | 'rgt' => 21,
17 | 'depth' => 3,
18 | 'name' => 'Node 2.1.2',
19 | ),
20 | 2 =>
21 | array (
22 | 'id' => 14,
23 | 'lft' => 24,
24 | 'rgt' => 25,
25 | 'depth' => 3,
26 | 'name' => 'Node 2.2.1',
27 | ),
28 | 3 =>
29 | array (
30 | 'id' => 15,
31 | 'lft' => 26,
32 | 'rgt' => 27,
33 | 'depth' => 3,
34 | 'name' => 'Node 2.2.2',
35 | ),
36 | );
--------------------------------------------------------------------------------
/tests/data/test-make-root-exists.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/data/test-make-root-new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/data/test-next-multiple-tree.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 38,
7 | 'tree' => 23,
8 | 'lft' => 30,
9 | 'rgt' => 43,
10 | 'depth' => 1,
11 | 'name' => 'Node 3',
12 | ),
13 | );
--------------------------------------------------------------------------------
/tests/data/test-next.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 16,
7 | 'lft' => 30,
8 | 'rgt' => 43,
9 | 'depth' => 1,
10 | 'name' => 'Node 3',
11 | ),
12 | );
--------------------------------------------------------------------------------
/tests/data/test-parents-multiple-tree-with-depth.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 32,
7 | 'tree' => 23,
8 | 'lft' => 17,
9 | 'rgt' => 22,
10 | 'depth' => 2,
11 | 'name' => 'Node 2.1',
12 | ),
13 | );
--------------------------------------------------------------------------------
/tests/data/test-parents-multiple-tree.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 23,
7 | 'tree' => 23,
8 | 'lft' => 1,
9 | 'rgt' => 44,
10 | 'depth' => 0,
11 | 'name' => 'Root 2',
12 | ),
13 | 1 =>
14 | array (
15 | 'id' => 31,
16 | 'tree' => 23,
17 | 'lft' => 16,
18 | 'rgt' => 29,
19 | 'depth' => 1,
20 | 'name' => 'Node 2',
21 | ),
22 | 2 =>
23 | array (
24 | 'id' => 32,
25 | 'tree' => 23,
26 | 'lft' => 17,
27 | 'rgt' => 22,
28 | 'depth' => 2,
29 | 'name' => 'Node 2.1',
30 | ),
31 | );
--------------------------------------------------------------------------------
/tests/data/test-parents-with-depth.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 10,
7 | 'lft' => 17,
8 | 'rgt' => 22,
9 | 'depth' => 2,
10 | 'name' => 'Node 2.1',
11 | ),
12 | );
--------------------------------------------------------------------------------
/tests/data/test-parents.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 1,
7 | 'lft' => 1,
8 | 'rgt' => 44,
9 | 'depth' => 0,
10 | 'name' => 'Root',
11 | ),
12 | 1 =>
13 | array (
14 | 'id' => 9,
15 | 'lft' => 16,
16 | 'rgt' => 29,
17 | 'depth' => 1,
18 | 'name' => 'Node 2',
19 | ),
20 | 2 =>
21 | array (
22 | 'id' => 10,
23 | 'lft' => 17,
24 | 'rgt' => 22,
25 | 'depth' => 2,
26 | 'name' => 'Node 2.1',
27 | ),
28 | );
--------------------------------------------------------------------------------
/tests/data/test-prepend-to-exists-another-tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/tests/data/test-prepend-to-exists-down.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-prepend-to-exists-up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/data/test-prepend-to-new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/tests/data/test-prev-multiple-tree.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 24,
7 | 'tree' => 23,
8 | 'lft' => 2,
9 | 'rgt' => 15,
10 | 'depth' => 1,
11 | 'name' => 'Node 1',
12 | ),
13 | );
--------------------------------------------------------------------------------
/tests/data/test-prev.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 2,
7 | 'lft' => 2,
8 | 'rgt' => 15,
9 | 'depth' => 1,
10 | 'name' => 'Node 1',
11 | ),
12 | );
--------------------------------------------------------------------------------
/tests/data/test-roots-multiple-tree-query.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 1,
7 | 'tree' => 1,
8 | 'lft' => 1,
9 | 'rgt' => 44,
10 | 'depth' => 0,
11 | 'name' => 'Root 1',
12 | ),
13 | 1 =>
14 | array (
15 | 'id' => 23,
16 | 'tree' => 23,
17 | 'lft' => 1,
18 | 'rgt' => 44,
19 | 'depth' => 0,
20 | 'name' => 'Root 2',
21 | ),
22 | 2 =>
23 | array (
24 | 'id' => 45,
25 | 'tree' => 45,
26 | 'lft' => 1,
27 | 'rgt' => 44,
28 | 'depth' => 0,
29 | 'name' => 'Root 3',
30 | ),
31 | );
--------------------------------------------------------------------------------
/tests/data/test-roots-query.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'id' => 1,
7 | 'lft' => 1,
8 | 'rgt' => 44,
9 | 'depth' => 0,
10 | 'name' => 'Root',
11 | ),
12 | );
--------------------------------------------------------------------------------
/tests/data/test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/tests/migrations/mysql.sql:
--------------------------------------------------------------------------------
1 | /**
2 | * MySQL
3 | */
4 |
5 | DROP TABLE IF EXISTS `tree`;
6 |
7 | CREATE TABLE `tree` (
8 | `id` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
9 | `lft` INT(11) NOT NULL,
10 | `rgt` INT(11) NOT NULL,
11 | `depth` INT(11) NOT NULL,
12 | `name` VARCHAR(255) NOT NULL
13 | );
14 |
15 | DROP TABLE IF EXISTS `multiple_tree`;
16 |
17 | CREATE TABLE `multiple_tree` (
18 | `id` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
19 | `tree` INT(11),
20 | `lft` INT(11) NOT NULL,
21 | `rgt` INT(11) NOT NULL,
22 | `depth` INT(11) NOT NULL,
23 | `name` VARCHAR(255) NOT NULL
24 | );
25 |
--------------------------------------------------------------------------------
/tests/migrations/sqlite.sql:
--------------------------------------------------------------------------------
1 | /**
2 | * SQLite
3 | */
4 |
5 | DROP TABLE IF EXISTS "tree";
6 |
7 | CREATE TABLE "tree" (
8 | "id" INTEGER NOT NULL PRIMARY KEY,
9 | "lft" INTEGER NOT NULL,
10 | "rgt" INTEGER NOT NULL,
11 | "depth" INTEGER NOT NULL,
12 | "name" TEXT NOT NULL
13 | );
14 |
15 | DROP TABLE IF EXISTS "multiple_tree";
16 |
17 | CREATE TABLE "multiple_tree" (
18 | "id" INTEGER NOT NULL PRIMARY KEY,
19 | "tree" INTEGER,
20 | "lft" INTEGER NOT NULL,
21 | "rgt" INTEGER NOT NULL,
22 | "depth" INTEGER NOT NULL,
23 | "name" TEXT NOT NULL
24 | );
25 |
--------------------------------------------------------------------------------
/tests/models/MultipleTree.php:
--------------------------------------------------------------------------------
1 | NestedSetsBehavior::className(),
40 | 'treeAttribute' => 'tree',
41 | ],
42 | ];
43 | }
44 |
45 | /**
46 | * @inheritdoc
47 | */
48 | public function rules()
49 | {
50 | return [
51 | ['name', 'required'],
52 | ];
53 | }
54 |
55 | /**
56 | * @inheritdoc
57 | */
58 | public function transactions()
59 | {
60 | return [
61 | self::SCENARIO_DEFAULT => self::OP_ALL,
62 | ];
63 | }
64 |
65 | /**
66 | * @inheritdoc
67 | */
68 | public static function find()
69 | {
70 | return new MultipleTreeQuery(get_called_class());
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/models/MultipleTreeQuery.php:
--------------------------------------------------------------------------------
1 | self::OP_ALL,
58 | ];
59 | }
60 |
61 | /**
62 | * @inheritdoc
63 | */
64 | public static function find()
65 | {
66 | return new TreeQuery(get_called_class());
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/models/TreeQuery.php:
--------------------------------------------------------------------------------
1 |