Skip to content

Commit

Permalink
Allow setting level (depth) of tree nodes on save.
Browse files Browse the repository at this point in the history
  • Loading branch information
ADmad committed Feb 7, 2015
1 parent 129bb18 commit 1726479
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 14 deletions.
82 changes: 80 additions & 2 deletions src/ORM/Behavior/TreeBehavior.php
Expand Up @@ -68,7 +68,8 @@ class TreeBehavior extends Behavior
'parent' => 'parent_id',
'left' => 'lft',
'right' => 'rght',
'scope' => null
'scope' => null,
'level' => null
];

/**
Expand All @@ -88,6 +89,7 @@ public function beforeSave(Event $event, Entity $entity)
$parent = $entity->get($config['parent']);
$primaryKey = $this->_getPrimaryKey();
$dirty = $entity->dirty($config['parent']);
$level = $config['level'];

if ($isNew && $parent) {
if ($entity->get($primaryKey[0]) == $parent) {
Expand All @@ -99,20 +101,92 @@ public function beforeSave(Event $event, Entity $entity)
$entity->set($config['left'], $edge);
$entity->set($config['right'], $edge + 1);
$this->_sync(2, '+', ">= {$edge}");

if ($level) {
$entity->set($config[$level], $parentNode[$level] + 1);
}
return;
}

if ($isNew && !$parent) {
$edge = $this->_getMax();
$entity->set($config['left'], $edge + 1);
$entity->set($config['right'], $edge + 2);

if ($level) {
$entity->set($config[$level], 0);
}
return;
}

if (!$isNew && $dirty && $parent) {
$this->_setParent($entity, $parent);

if ($level) {
$parentNode = $this->_getNode($parent);
$entity->set($config[$level], $parentNode[$level] + 1);
}
return;
}

if (!$isNew && $dirty && !$parent) {
$this->_setAsRoot($entity);

if ($level) {
$entity->set($config[$level], 0);
}
}
}

/**
* After save listener.
*
* Manages updating level of descendents of currently saved entity.
*
* @param \Cake\Event\Event $event The beforeSave event that was fired
* @param \Cake\ORM\Entity $entity the entity that is going to be saved
* @return void
*/
public function afterSave(Event $event, Entity $entity)
{
if (!$this->_config['level'] || $entity->isNew()) {
return;
}

$this->_setChildrenLevel($entity);
}

/**
* Set level for descendents.
*
* @param \Cake\ORM\Entity $entity
* @return void
*/
protected function _setChildrenLevel(Entity $entity)
{
$config = $this->config();

if ($entity->get($config['left']) + 1 === $entity->get($config['right'])) {
return;
}

$primaryKey = $this->_getPrimaryKey();
$primaryKeyValue = $entity->get($primaryKey);
$depths = [$primaryKeyValue => $entity->get($config['level'])];

$children = $this->_table->find('children', [
'for' => $primaryKeyValue,
'fields' => [$this->_getPrimaryKey(), $config['parent'], $config['level']],
'order' => $config['left']
]);

foreach ($children as $node) {
$parentIdValue = $node->get($config['parent']);
$depth = $depths[$parentIdValue] + 1;
$depths[$node->get($primaryKey)] = $depth;

$node->set($config['level'], $depth);
$this->_table->save($node, ['checkRules' => false, 'atomic' => false]);
}
}

Expand Down Expand Up @@ -624,9 +698,13 @@ protected function _getNode($id)
$config = $this->config();
list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
$primaryKey = $this->_getPrimaryKey();
$fields = [$parent, $left, $right];
if ($config['level']) {
$fields[] = $config['level'];
}

$node = $this->_scope($this->_table->find())
->select([$parent, $left, $right])
->select($fields)
->where([$this->_table->alias() . '.' . $primaryKey => $id])
->first();

Expand Down
34 changes: 23 additions & 11 deletions tests/Fixture/NumberTreesFixture.php
Expand Up @@ -36,6 +36,7 @@ class NumberTreesFixture extends TestFixture
'parent_id' => 'integer',
'lft' => ['type' => 'integer'],
'rght' => ['type' => 'integer'],
'level' => ['type' => 'integer'],
'_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
];

Expand All @@ -61,67 +62,78 @@ class NumberTreesFixture extends TestFixture
'name' => 'electronics',
'parent_id' => null,
'lft' => '1',
'rght' => '20'
'rght' => '20',
'level' => 0
],
[
'name' => 'televisions',
'parent_id' => '1',
'lft' => '2',
'rght' => '9'
'rght' => '9',
'level' => 1
],
[
'name' => 'tube',
'parent_id' => '2',
'lft' => '3',
'rght' => '4'
'rght' => '4',
'level' => 2
],
[
'name' => 'lcd',
'parent_id' => '2',
'lft' => '5',
'rght' => '6'
'rght' => '6',
'level' => 2
],
[
'name' => 'plasma',
'parent_id' => '2',
'lft' => '7',
'rght' => '8'
'rght' => '8',
'level' => 2
],
[
'name' => 'portable',
'parent_id' => '1',
'lft' => '10',
'rght' => '19'
'rght' => '19',
'level' => 1
],
[
'name' => 'mp3',
'parent_id' => '6',
'lft' => '11',
'rght' => '14'
'rght' => '14',
'level' => 2
],
[
'name' => 'flash',
'parent_id' => '7',
'lft' => '12',
'rght' => '13'
'rght' => '13',
'level' => 3
],
[
'name' => 'cd',
'parent_id' => '6',
'lft' => '15',
'rght' => '16'
'rght' => '16',
'level' => 2
],
[
'name' => 'radios',
'parent_id' => '6',
'lft' => '17',
'rght' => '18'
'rght' => '18',
'level' => 2
],
[
'name' => 'alien hardware',
'parent_id' => null,
'lft' => '21',
'rght' => '22'
'rght' => '22',
'level' => 0
]
];
}
59 changes: 58 additions & 1 deletion tests/TestCase/ORM/Behavior/TreeBehaviorTest.php
Expand Up @@ -502,7 +502,7 @@ public function testAddOrphan()
{
$table = $this->table;
$entity = new Entity(
['name' => 'New Orphan', 'parent_id' => null],
['name' => 'New Orphan', 'parent_id' => null, 'level' => null],
['markNew' => true]
);
$expected = $table->find()->order('lft')->hydrate(false)->toArray();
Expand Down Expand Up @@ -878,6 +878,63 @@ public function testGetLevel()
$this->assertFalse($result);
}

/**
* Test setting level for new nodes
*
* @return void
*/
public function testSetLevelNewNode()
{
$this->table->behaviors()->Tree->config('level', 'level');

$entity = new Entity(['parent_id' => null, 'name' => 'Depth 0']);
$this->table->save($entity);
$entity = $this->table->get(12);
$this->assertEquals(0, $entity->level);

$entity = new Entity(['parent_id' => 1, 'name' => 'Depth 1']);
$this->table->save($entity);
$entity = $this->table->get(13);
$this->assertEquals(1, $entity->level);

$entity = new Entity(['parent_id' => 8, 'name' => 'Depth 4']);
$this->table->save($entity);
$entity = $this->table->get(14);
$this->assertEquals(4, $entity->level);
}

/**
* Test setting level for existing nodes
*
* @return void
*/
public function testSetLevelExistingNode()
{
$this->table->behaviors()->Tree->config('level', 'level');

// Leaf node
$entity = $this->table->get(4);
$this->assertEquals(2, $entity->level);
$this->table->save($entity);
$entity = $this->table->get(4);
$this->assertEquals(2, $entity->level);

// Non leaf node so depth of descendents will also change
$entity = $this->table->get(6);
$this->assertEquals(1, $entity->level);

$entity->parent_id = null;
$this->table->save($entity);
$entity = $this->table->get(6);
$this->assertEquals(0, $entity->level);

$entity = $this->table->get(7);
$this->assertEquals(1, $entity->level);

$entity = $this->table->get(8);
$this->assertEquals(2, $entity->level);
}

/**
* Custom assertion use to verify tha a tree is returned in the expected order
* and that it is still valid
Expand Down

0 comments on commit 1726479

Please sign in to comment.