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
- hhvm
matrix:
allow_failures:
- php: 7.0
sudo: false
install:
- composer self-update
- composer global require fxp/composer-asset-plugin:~1.0
- composer install
- composer update --prefer-dist --no-interaction
before_script:
- mysql --version
- psql --version
- mysql -e '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:
- vendor/bin/phpunit --coverage-clover=coverage.clover
- vendor/bin/phpunit $PHPUNIT_FLAGS
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
*/
public function isRoot()

60
README.md

@ -23,12 +23,56 @@ or add
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;
- `m150722_150100_multiple_tree.php` - for multiple tree tables.
```php
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
@ -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
```
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:
```php

110
composer.lock generated

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

Loading…
Cancel
Save