|
@@ -4,16 +4,16 @@ namespace Gedmo\Tree\Strategy\ORM;
|
|
|
|
|
|
use Gedmo\Tree\Strategy,
|
|
|
Doctrine\ORM\EntityManager,
|
|
|
- Gedmo\Tree\AbstractTreeListener,
|
|
|
+ Gedmo\Tree\TreeListener,
|
|
|
Doctrine\ORM\Query;
|
|
|
|
|
|
/**
|
|
|
* This strategy makes tree act like
|
|
|
* nested set.
|
|
|
- *
|
|
|
+ *
|
|
|
* This behavior can inpact the performance of your application
|
|
|
* since nested set trees are slow on inserts and updates.
|
|
|
- *
|
|
|
+ *
|
|
|
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
|
|
* @package Gedmo.Tree.Strategy.ORM
|
|
|
* @subpackage Nested
|
|
@@ -26,72 +26,72 @@ class Nested implements Strategy
|
|
|
* Previous sibling position
|
|
|
*/
|
|
|
const PREV_SIBLING = 'prevSibling';
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Next sibling position
|
|
|
*/
|
|
|
const NEXT_SIBLING = 'nextSibling';
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Last child position
|
|
|
*/
|
|
|
const LAST_CHILD = 'lastChild';
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* First child position
|
|
|
*/
|
|
|
const FIRST_CHILD = 'firstChild';
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* TreeListener
|
|
|
- *
|
|
|
+ *
|
|
|
* @var AbstractTreeListener
|
|
|
*/
|
|
|
protected $listener = null;
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* The max number of "right" field of the
|
|
|
* tree in case few root nodes will be persisted
|
|
|
* on one flush for node classes
|
|
|
- *
|
|
|
+ *
|
|
|
* @var array
|
|
|
*/
|
|
|
private $treeEdges = array();
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* List of pending Nodes, which needs to
|
|
|
* be post processed because of having a parent Node
|
|
|
* which requires some additional calculations
|
|
|
- *
|
|
|
+ *
|
|
|
* @var array
|
|
|
*/
|
|
|
private $pendingChildNodeInserts = array();
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* List of persisted nodes for specific
|
|
|
* class to know when to process pending
|
|
|
* inserts
|
|
|
- *
|
|
|
+ *
|
|
|
* @var array
|
|
|
*/
|
|
|
private $persistedNodes = array();
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Number of updates for specific
|
|
|
* classes to know if refresh is necessary
|
|
|
- *
|
|
|
+ *
|
|
|
* @var array
|
|
|
*/
|
|
|
private $updatesOnNodeClasses = array();
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
|
- public function __construct(AbstractTreeListener $listener)
|
|
|
+ public function __construct(TreeListener $listener)
|
|
|
{
|
|
|
$this->listener = $listener;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
@@ -99,7 +99,7 @@ class Nested implements Strategy
|
|
|
{
|
|
|
return Strategy::NESTED;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
@@ -108,7 +108,7 @@ class Nested implements Strategy
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
$uow = $em->getUnitOfWork();
|
|
|
-
|
|
|
+
|
|
|
$changeSet = $uow->getEntityChangeSet($node);
|
|
|
if (isset($config['root']) && isset($changeSet[$config['root']])) {
|
|
|
throw new \Gedmo\Exception\UnexpectedValueException("Root cannot be changed manualy, change parent instead");
|
|
@@ -119,18 +119,18 @@ class Nested implements Strategy
|
|
|
$this->updateNode($em, $node, $changeSet[$config['parent']][1]);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
|
- public function processPostPersist($em, $node)
|
|
|
+ public function processPostPersist($em, $node)
|
|
|
{
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
|
|
|
if (isset($config['root'])) {
|
|
|
$parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
|
|
|
-
|
|
|
+
|
|
|
$identifierField = $meta->getSingleIdentifierFieldName();
|
|
|
$nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
|
|
|
if ($parent) {
|
|
@@ -145,7 +145,7 @@ class Nested implements Strategy
|
|
|
$em->createQuery($dql)->getSingleScalarResult();
|
|
|
}
|
|
|
unset($this->persistedNodes[spl_object_hash($node)]);
|
|
|
-
|
|
|
+
|
|
|
if (!$this->persistedNodes && $this->pendingChildNodeInserts) {
|
|
|
$pendingChildNodeInserts = $this->pendingChildNodeInserts;
|
|
|
foreach ($pendingChildNodeInserts as $class => &$nodes) {
|
|
@@ -156,7 +156,7 @@ class Nested implements Strategy
|
|
|
$this->pendingChildNodeInserts = array();
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
@@ -165,10 +165,10 @@ class Nested implements Strategy
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
$uow = $em->getUnitOfWork();
|
|
|
-
|
|
|
+
|
|
|
$leftValue = $meta->getReflectionProperty($config['left'])->getValue($node);
|
|
|
$rightValue = $meta->getReflectionProperty($config['right'])->getValue($node);
|
|
|
-
|
|
|
+
|
|
|
if (!$leftValue || !$rightValue) {
|
|
|
return;
|
|
|
}
|
|
@@ -189,10 +189,10 @@ class Nested implements Strategy
|
|
|
$uow->scheduleForDelete($removalNode);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
$this->shiftRL($em, $meta->rootEntityName, $rightValue + 1, -$diff, $rootId);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
@@ -202,7 +202,7 @@ class Nested implements Strategy
|
|
|
$this->treeEdges = array();
|
|
|
$this->updatesOnNodeClasses = array();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* {@inheritdoc}
|
|
|
*/
|
|
@@ -210,7 +210,7 @@ class Nested implements Strategy
|
|
|
{
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
-
|
|
|
+
|
|
|
$parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
|
|
|
if ($parent === null) {
|
|
|
$this->prepareRoot($em, $node);
|
|
@@ -230,11 +230,11 @@ class Nested implements Strategy
|
|
|
}
|
|
|
$this->persistedNodes[spl_object_hash($node)] = null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Insert a node which requires
|
|
|
* parent synchronization
|
|
|
- *
|
|
|
+ *
|
|
|
* @param EntityManager $em
|
|
|
* @param object $node
|
|
|
* @return void
|
|
@@ -243,18 +243,18 @@ class Nested implements Strategy
|
|
|
{
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
-
|
|
|
+
|
|
|
$parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
|
|
|
$identifierField = $meta->getSingleIdentifierFieldName();
|
|
|
$nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
|
|
|
-
|
|
|
+
|
|
|
if (isset($this->pendingChildNodeInserts[$meta->name]) && count($this->pendingChildNodeInserts[$meta->name]) > 1) {
|
|
|
$em->refresh($parent);
|
|
|
}
|
|
|
$rootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($parent) : null;
|
|
|
$parentRight = $meta->getReflectionProperty($config['right'])->getValue($parent);
|
|
|
$this->shiftRL($em, $meta->rootEntityName, $parentRight, 2, $rootId);
|
|
|
-
|
|
|
+
|
|
|
$meta->getReflectionProperty($config['left'])->setValue($node, $parentRight);
|
|
|
$meta->getReflectionProperty($config['right'])->setValue($node, $parentRight + 1);
|
|
|
$dql = "UPDATE {$meta->rootEntityName} node";
|
|
@@ -263,11 +263,11 @@ class Nested implements Strategy
|
|
|
$dql .= " WHERE node.{$identifierField} = {$nodeId}";
|
|
|
$em->createQuery($dql)->getSingleScalarResult();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Update the $node with a diferent $parent
|
|
|
* destination
|
|
|
- *
|
|
|
+ *
|
|
|
* @todo consider $position configurable through listener
|
|
|
* @param EntityManager $em
|
|
|
* @param object $node - target node
|
|
@@ -280,18 +280,18 @@ class Nested implements Strategy
|
|
|
{
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
-
|
|
|
+
|
|
|
// if there is more than one update, need to refresh node
|
|
|
if (!isset($this->updatesOnNodeClasses[$meta->name]) || $this->updatesOnNodeClasses[$meta->name] > 1) {
|
|
|
$em->refresh($node);
|
|
|
}
|
|
|
$rootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null;
|
|
|
$identifierField = $meta->getSingleIdentifierFieldName();
|
|
|
- $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
|
|
|
-
|
|
|
+ $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
|
|
|
+
|
|
|
$left = $meta->getReflectionProperty($config['left'])->getValue($node);
|
|
|
$right = $meta->getReflectionProperty($config['right'])->getValue($node);
|
|
|
-
|
|
|
+
|
|
|
$level = 0;
|
|
|
$treeSize = $right - $left + 1;
|
|
|
$newRootId = null;
|
|
@@ -312,15 +312,15 @@ class Nested implements Strategy
|
|
|
case self::PREV_SIBLING:
|
|
|
$start = $parentLeft;
|
|
|
break;
|
|
|
-
|
|
|
+
|
|
|
case self::NEXT_SIBLING:
|
|
|
$start = $parentRight + 1;
|
|
|
break;
|
|
|
-
|
|
|
+
|
|
|
case self::LAST_CHILD:
|
|
|
$start = $parentRight;
|
|
|
$level++;
|
|
|
-
|
|
|
+
|
|
|
case self::FIRST_CHILD:
|
|
|
default:
|
|
|
$start = $parentLeft + 1;
|
|
@@ -343,7 +343,7 @@ class Nested implements Strategy
|
|
|
$start = 1;
|
|
|
$newRootId = $nodeId;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
$diff = $start - $left;
|
|
|
$qb = $em->createQueryBuilder();
|
|
|
$qb->update($meta->rootEntityName, 'node');
|
|
@@ -356,10 +356,10 @@ class Nested implements Strategy
|
|
|
if ($treeSize > 2) {
|
|
|
$levelDiff = isset($config['level']) ? $level - $meta->getReflectionProperty($config['level'])->getValue($node) : null;
|
|
|
$this->shiftRangeRL(
|
|
|
- $em,
|
|
|
- $meta->rootEntityName,
|
|
|
- $left,
|
|
|
- $right,
|
|
|
+ $em,
|
|
|
+ $meta->rootEntityName,
|
|
|
+ $left,
|
|
|
+ $right,
|
|
|
$diff,
|
|
|
$rootId,
|
|
|
$newRootId,
|
|
@@ -369,15 +369,15 @@ class Nested implements Strategy
|
|
|
$qb->set('node.' . $config['left'], $left + $diff);
|
|
|
$qb->set('node.' . $config['right'], $right + $diff);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
$qb->where("node.{$identifierField} = {$nodeId}");
|
|
|
$qb->getQuery()->getSingleScalarResult();
|
|
|
$this->shiftRL($em, $meta->rootEntityName, $left, -$treeSize, $rootId);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Get the edge of tree
|
|
|
- *
|
|
|
+ *
|
|
|
* @param EntityManager $em
|
|
|
* @param string $class
|
|
|
* @param integer $rootId
|
|
@@ -387,20 +387,20 @@ class Nested implements Strategy
|
|
|
{
|
|
|
$meta = $em->getClassMetadata($class);
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
-
|
|
|
+
|
|
|
$dql = "SELECT MAX(node.{$config['right']}) FROM {$meta->rootEntityName} node";
|
|
|
if (isset($config['root']) && $rootId) {
|
|
|
$dql .= " WHERE node.{$config['root']} = {$rootId}";
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
$query = $em->createQuery($dql);
|
|
|
$right = $query->getSingleScalarResult();
|
|
|
return intval($right);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Shift tree left and right values by delta
|
|
|
- *
|
|
|
+ *
|
|
|
* @param EntityManager $em
|
|
|
* @param string $class
|
|
|
* @param integer $first
|
|
@@ -408,14 +408,14 @@ class Nested implements Strategy
|
|
|
* @param integer $rootId
|
|
|
* @return void
|
|
|
*/
|
|
|
- public function shiftRL(EntityManager $em, $class, $first, $delta, $rootId = null)
|
|
|
+ public function shiftRL(EntityManager $em, $class, $first, $delta, $rootId = null)
|
|
|
{
|
|
|
$meta = $em->getClassMetadata($class);
|
|
|
$config = $this->listener->getConfiguration($em, $class);
|
|
|
-
|
|
|
+
|
|
|
$sign = ($delta >= 0) ? ' + ' : ' - ';
|
|
|
$delta = abs($delta);
|
|
|
-
|
|
|
+
|
|
|
$dql = "UPDATE {$meta->rootEntityName} node";
|
|
|
$dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$delta}";
|
|
|
$dql .= " WHERE node.{$config['left']} >= {$first}";
|
|
@@ -424,7 +424,7 @@ class Nested implements Strategy
|
|
|
}
|
|
|
$q = $em->createQuery($dql);
|
|
|
$q->getSingleScalarResult();
|
|
|
-
|
|
|
+
|
|
|
$dql = "UPDATE {$meta->rootEntityName} node";
|
|
|
$dql .= " SET node.{$config['right']} = node.{$config['right']} {$sign} {$delta}";
|
|
|
$dql .= " WHERE node.{$config['right']} >= {$first}";
|
|
@@ -434,11 +434,11 @@ class Nested implements Strategy
|
|
|
$q = $em->createQuery($dql);
|
|
|
$q->getSingleScalarResult();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Shift range of right and left values on tree
|
|
|
* depending on tree level diference also
|
|
|
- *
|
|
|
+ *
|
|
|
* @param EntityManager $em
|
|
|
* @param string $class
|
|
|
* @param integer $first
|
|
@@ -453,12 +453,12 @@ class Nested implements Strategy
|
|
|
{
|
|
|
$meta = $em->getClassMetadata($class);
|
|
|
$config = $this->listener->getConfiguration($em, $class);
|
|
|
-
|
|
|
+
|
|
|
$sign = ($delta >= 0) ? ' + ' : ' - ';
|
|
|
$delta = abs($delta);
|
|
|
$levelSign = ($levelDelta >= 0) ? ' + ' : ' - ';
|
|
|
$levelDelta = abs($levelDelta);
|
|
|
-
|
|
|
+
|
|
|
$dql = "UPDATE {$meta->rootEntityName} node";
|
|
|
$dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$delta}";
|
|
|
$dql .= ", node.{$config['right']} = node.{$config['right']} {$sign} {$delta}";
|
|
@@ -476,10 +476,10 @@ class Nested implements Strategy
|
|
|
$q = $em->createQuery($dql);
|
|
|
$q->getSingleScalarResult();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* If Node does not have parent, set it as root
|
|
|
- *
|
|
|
+ *
|
|
|
* @param EntityManager $em
|
|
|
* @param object $entity
|
|
|
* @return void
|
|
@@ -488,13 +488,13 @@ class Nested implements Strategy
|
|
|
{
|
|
|
$meta = $em->getClassMetadata(get_class($node));
|
|
|
$config = $this->listener->getConfiguration($em, $meta->name);
|
|
|
-
|
|
|
+
|
|
|
if (isset($config['root'])) {
|
|
|
$meta->getReflectionProperty($config['root'])->setValue($node, null);
|
|
|
$meta->getReflectionProperty($config['left'])->setValue($node, 1);
|
|
|
$meta->getReflectionProperty($config['right'])->setValue($node, 2);
|
|
|
} else {
|
|
|
- $edge = isset($this->treeEdges[$meta->name]) ?
|
|
|
+ $edge = isset($this->treeEdges[$meta->name]) ?
|
|
|
$this->treeEdges[$meta->name] : $this->max($em, $meta->rootEntityName);
|
|
|
$meta->getReflectionProperty($config['left'])->setValue($node, $edge + 1);
|
|
|
$meta->getReflectionProperty($config['right'])->setValue($node, $edge + 2);
|