Browse Source

Add populateTree() method

tags/v1.0.2 v1.0.2
PaulZi 9 years ago
parent
commit
d8cf22b531
  1. 19
      .travis.yml
  2. 47
      NestedSetsBehavior.php
  3. 60
      README.md
  4. 110
      composer.lock
  5. 25
      sample-migrations/m150722_150000_single_tree.php
  6. 26
      sample-migrations/m150722_150100_multiple_tree.php
  7. 19
      tests/NestedSetsBehaviorTestCase.php

19
.travis.yml

@ -7,26 +7,29 @@ php:
- 7.0 - 7.0
- hhvm - hhvm
matrix:
allow_failures:
- php: 7.0
sudo: false sudo: false
install: install:
- composer self-update - composer self-update
- composer global require fxp/composer-asset-plugin:~1.0 - composer global require fxp/composer-asset-plugin:~1.0
- composer install - composer update --prefer-dist --no-interaction
before_script: before_script:
- mysql --version - mysql --version
- psql --version - psql --version
- mysql -e 'create database test;' - mysql -e 'create database test;'
- psql -U postgres -c 'CREATE DATABASE test;'; - psql -U postgres -c 'CREATE DATABASE test;';
- |
if [ $TRAVIS_PHP_VERSION = '5.6' ]; then
PHPUNIT_FLAGS="--coverage-clover=coverage.clover"
fi
script: script:
- vendor/bin/phpunit --coverage-clover=coverage.clover - vendor/bin/phpunit $PHPUNIT_FLAGS
after_script: after_script:
- wget https://scrutinizer-ci.com/ocular.phar - |
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover if [ $TRAVIS_PHP_VERSION = '5.6' ]; then
wget https://scrutinizer-ci.com/ocular.phar
php ocular.phar code-coverage:upload --format=php-clover coverage.clover
fi

47
NestedSetsBehavior.php

@ -213,6 +213,53 @@ class NestedSetsBehavior extends Behavior
} }
/** /**
* Populate children relations for self and all descendants
* @param int $depth = null
* @return static
*/
public function populateTree($depth = null)
{
/** @var ActiveRecord[]|static[] $nodes */
if ($depth === null) {
$nodes = $this->owner->descendants;
} else {
$nodes = $this->getDescendants($depth)->all();
}
$relates = [];
$parents = [$this->owner->getAttribute($this->leftAttribute)];
$prev = $this->owner->getAttribute($this->depthAttribute);
foreach($nodes as $node)
{
$depth = $node->getAttribute($this->depthAttribute);
if ($depth <= $prev) {
$parents = array_slice($parents, 0, $depth - $prev - 1);
}
$key = end($parents);
if (!isset($relates[$key])) {
$relates[$key] = [];
}
$relates[$key][] = $node;
$parents[] = $node->getAttribute($this->leftAttribute);
$prev = $depth;
}
$nodes[$this->owner->getAttribute($this->leftAttribute)] = $this->owner;
foreach ($nodes as $node) {
$key = $node->getAttribute($this->leftAttribute);
if (isset($relates[$key])) {
$node->populateRelation('children', $relates[$key]);
} elseif ($depth === null) {
$node->populateRelation('children', []);
}
}
return $this->owner;
}
/**
* @return bool * @return bool
*/ */
public function isRoot() public function isRoot()

60
README.md

@ -23,12 +23,56 @@ or add
to the `require` section of your `composer.json` file. to the `require` section of your `composer.json` file.
## Migrations ## Migrations example
Sample migrations are in the folder `sample-migrations`: Single tree migration:
- `m150722_150000_single_tree.php` - for single tree tables; ```php
- `m150722_150100_multiple_tree.php` - for multiple tree tables. class m150722_150000_single_tree extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%single_tree}}', [
'id' => Schema::TYPE_PK,
'lft' => Schema::TYPE_INTEGER . ' NOT NULL',
'rgt' => Schema::TYPE_INTEGER . ' NOT NULL',
'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
], $tableOptions);
$this->createIndex('lft', '{{%single_tree}}', ['lft', 'rgt']);
$this->createIndex('rgt', '{{%single_tree}}', ['rgt']);
}
}
```
Multiple tree migration:
```php
class m150722_150100_multiple_tree extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%multiple_tree}}', [
'id' => Schema::TYPE_PK,
'tree' => Schema::TYPE_INTEGER . ' NULL',
'lft' => Schema::TYPE_INTEGER . ' NOT NULL',
'rgt' => Schema::TYPE_INTEGER . ' NOT NULL',
'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
], $tableOptions);
$this->createIndex('lft', '{{%multiple_tree}}', ['tree', 'lft', 'rgt']);
$this->createIndex('rgt', '{{%multiple_tree}}', ['tree', 'rgt']);
}
}
```
## Configuring ## Configuring
@ -136,6 +180,14 @@ $descendants = $node11->getDescendants(2, true)->all(); // get 2 levels of desce
$descendants = $node11->getDescendants(3, false, true)->all(); // get 3 levels of descendants in back order $descendants = $node11->getDescendants(3, false, true)->all(); // get 3 levels of descendants in back order
``` ```
To populate `children` relations for self and descendants of a node:
```php
$node11 = Sample::findOne(['name' => 'node 1.1']);
$tree = $node11->populateTree(); // populate all levels
$tree = $node11->populateTree(2); // populate 2 levels of descendants
```
To get the children of a node: To get the children of a node:
```php ```php

110
composer.lock generated

@ -133,16 +133,16 @@
}, },
{ {
"name": "bower-asset/yii2-pjax", "name": "bower-asset/yii2-pjax",
"version": "v2.0.4", "version": "v2.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/yiisoft/jquery-pjax.git", "url": "https://github.com/yiisoft/jquery-pjax.git",
"reference": "3f20897307cca046fca5323b318475ae9dac0ca0" "reference": "6818718408086db6bdcf33649cecb86b6b4f9b67"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/3f20897307cca046fca5323b318475ae9dac0ca0", "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/6818718408086db6bdcf33649cecb86b6b4f9b67",
"reference": "3f20897307cca046fca5323b318475ae9dac0ca0", "reference": "6818718408086db6bdcf33649cecb86b6b4f9b67",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -629,16 +629,16 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "2.2.3", "version": "2.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "ef1ca6835468857944d5c3b48fa503d5554cff2f" "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef1ca6835468857944d5c3b48fa503d5554cff2f", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "ef1ca6835468857944d5c3b48fa503d5554cff2f", "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -687,7 +687,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2015-09-14 06:51:16" "time": "2015-10-06 15:47:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -869,16 +869,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "4.8.9", "version": "4.8.20",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "73fad41adb5b7bc3a494bb930d90648df1d5e74b" "reference": "7438c43bc2bbb2febe1723eb595b1c49283a26ad"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/73fad41adb5b7bc3a494bb930d90648df1d5e74b", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7438c43bc2bbb2febe1723eb595b1c49283a26ad",
"reference": "73fad41adb5b7bc3a494bb930d90648df1d5e74b", "reference": "7438c43bc2bbb2febe1723eb595b1c49283a26ad",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -887,7 +887,7 @@
"ext-pcre": "*", "ext-pcre": "*",
"ext-reflection": "*", "ext-reflection": "*",
"ext-spl": "*", "ext-spl": "*",
"php": ">=5.3.3", "php": "~5.3.3|~5.4|~5.5|~5.6",
"phpspec/prophecy": "^1.3.1", "phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "~2.1", "phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4", "phpunit/php-file-iterator": "~1.4",
@ -937,20 +937,20 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2015-09-20 12:56:44" "time": "2015-12-10 07:48:52"
}, },
{ {
"name": "phpunit/phpunit-mock-objects", "name": "phpunit/phpunit-mock-objects",
"version": "2.3.7", "version": "2.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "5e2645ad49d196e020b85598d7c97e482725786a" "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5e2645ad49d196e020b85598d7c97e482725786a", "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"reference": "5e2645ad49d196e020b85598d7c97e482725786a", "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -993,7 +993,7 @@
"mock", "mock",
"xunit" "xunit"
], ],
"time": "2015-08-19 09:14:08" "time": "2015-10-02 06:51:40"
}, },
{ {
"name": "sebastian/comparator", "name": "sebastian/comparator",
@ -1061,28 +1061,28 @@
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
"version": "1.3.0", "version": "1.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/diff.git", "url": "https://github.com/sebastianbergmann/diff.git",
"reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
"reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.2" "phpunit/phpunit": "~4.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.3-dev" "dev-master": "1.4-dev"
} }
}, },
"autoload": { "autoload": {
@ -1105,24 +1105,24 @@
} }
], ],
"description": "Diff implementation", "description": "Diff implementation",
"homepage": "http://www.github.com/sebastianbergmann/diff", "homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [ "keywords": [
"diff" "diff"
], ],
"time": "2015-02-22 15:13:53" "time": "2015-12-08 07:14:41"
}, },
{ {
"name": "sebastian/environment", "name": "sebastian/environment",
"version": "1.3.2", "version": "1.3.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/environment.git", "url": "https://github.com/sebastianbergmann/environment.git",
"reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" "reference": "6e7133793a8e5a5714a551a8324337374be209df"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
"reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", "reference": "6e7133793a8e5a5714a551a8324337374be209df",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1159,7 +1159,7 @@
"environment", "environment",
"hhvm" "hhvm"
], ],
"time": "2015-08-03 06:14:51" "time": "2015-12-02 08:37:27"
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
@ -1229,16 +1229,16 @@
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",
"version": "1.0.0", "version": "1.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git", "url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1276,20 +1276,20 @@
"keywords": [ "keywords": [
"global state" "global state"
], ],
"time": "2014-10-06 09:23:50" "time": "2015-10-12 03:26:01"
}, },
{ {
"name": "sebastian/recursion-context", "name": "sebastian/recursion-context",
"version": "1.0.1", "version": "1.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git", "url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "994d4a811bafe801fb06dccbee797863ba2792ba" "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
"reference": "994d4a811bafe801fb06dccbee797863ba2792ba", "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1329,7 +1329,7 @@
], ],
"description": "Provides functionality to recursively process PHP variables", "description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context", "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-06-21 08:04:50" "time": "2015-11-11 19:50:13"
}, },
{ {
"name": "sebastian/version", "name": "sebastian/version",
@ -1368,34 +1368,34 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v2.7.4", "version": "v3.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/Yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "2dc7b06c065df96cc686c66da2705e5e18aef661" "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/2dc7b06c065df96cc686c66da2705e5e18aef661", "url": "https://api.github.com/repos/symfony/yaml/zipball/177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002",
"reference": "2dc7b06c065df96cc686c66da2705e5e18aef661", "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9" "php": ">=5.5.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.7-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Symfony\\Component\\Yaml\\": "" "Symfony\\Component\\Yaml\\": ""
} },
"exclude-from-classmap": [
"/Tests/"
]
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
@ -1413,7 +1413,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2015-08-24 07:13:45" "time": "2015-11-30 12:36:17"
} }
], ],
"aliases": [], "aliases": [],

25
sample-migrations/m150722_150000_single_tree.php

@ -1,25 +0,0 @@
<?php
use yii\db\Schema;
use yii\db\Migration;
class m150722_150000_single_tree extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%single_tree}}', [
'id' => Schema::TYPE_PK,
'lft' => Schema::TYPE_INTEGER . ' NOT NULL',
'rgt' => Schema::TYPE_INTEGER . ' NOT NULL',
'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
], $tableOptions);
$this->createIndex('lft', '{{%single_tree}}', ['lft', 'rgt']);
$this->createIndex('rgt', '{{%single_tree}}', ['rgt']);
}
}

26
sample-migrations/m150722_150100_multiple_tree.php

@ -1,26 +0,0 @@
<?php
use yii\db\Schema;
use yii\db\Migration;
class m150722_150100_multiple_tree extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%multiple_tree}}', [
'id' => Schema::TYPE_PK,
'tree' => Schema::TYPE_INTEGER . ' NULL',
'lft' => Schema::TYPE_INTEGER . ' NOT NULL',
'rgt' => Schema::TYPE_INTEGER . ' NOT NULL',
'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
], $tableOptions);
$this->createIndex('lft', '{{%multiple_tree}}', ['tree', 'lft', 'rgt']);
$this->createIndex('rgt', '{{%multiple_tree}}', ['tree', 'rgt']);
}
}

19
tests/NestedSetsBehaviorTestCase.php

@ -115,6 +115,25 @@ class NestedSetsBehaviorTestCase extends BaseTestCase
$this->assertEquals(null, MultipleTreeNode::findOne(19)->getNext()->one()); $this->assertEquals(null, MultipleTreeNode::findOne(19)->getNext()->one());
} }
public function testPopulateTree()
{
$node = Node::findOne(2);
$node->populateTree();
$this->assertEquals(true, $node->isRelationPopulated('children'));
$this->assertEquals(true, $node->children[0]->isRelationPopulated('children'));
$this->assertEquals(11, $node->children[0]->children[0]->id);
$node = MultipleTreeNode::findOne(2);
$node->populateTree(1);
$this->assertEquals(true, $node->isRelationPopulated('children'));
$this->assertEquals(false, $node->children[0]->isRelationPopulated('children'));
$this->assertEquals(5, $node->children[0]->id);
$node = Node::findOne(19);
$node->populateTree();
$this->assertEquals(true, $node->isRelationPopulated('children'));
}
public function testIsRoot() public function testIsRoot()
{ {
$this->assertTrue(Node::findOne(1)->isRoot()); $this->assertTrue(Node::findOne(1)->isRoot());

Loading…
Cancel
Save