diff --git a/README.markdown b/README.markdown index 961b862537..f03f394a42 100644 --- a/README.markdown +++ b/README.markdown @@ -25,8 +25,9 @@ use these extensions from separate branch **doctrine2.0.x** or simply checkout t ### Latest updates -**2011-04-04** +**2011-04-11** +- **Tree nestedset** was improved, now all in memory nodes are synchronized and do not require `$em->clear()` all the time - Extensions now use only one listener instance for different object managers ### ODM MongoDB support diff --git a/lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php b/lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php index e4af625dc0..7b79cc48f0 100644 --- a/lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php +++ b/lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php @@ -236,11 +236,7 @@ public function getNextSiblings($node, $includeSelf = false) if (!$parent) { throw new InvalidArgumentException("Cannot get siblings from tree root node"); } - $identifierField = $meta->getSingleIdentifierFieldName(); - if ($parent instanceof Proxy) { - $this->_em->refresh($parent); - } - $parentId = $meta->getReflectionProperty($identifierField)->getValue($parent); + $parentId = current($this->_em->getUnitOfWork()->getEntityIdentifier($parent)); $left = $meta->getReflectionProperty($config['left'])->getValue($node); $sign = $includeSelf ? '>=' : '>'; @@ -275,11 +271,7 @@ public function getPrevSiblings($node, $includeSelf = false) if (!$parent) { throw new InvalidArgumentException("Cannot get siblings from tree root node"); } - $identifierField = $meta->getSingleIdentifierFieldName(); - if ($parent instanceof Proxy) { - $this->_em->refresh($parent); - } - $parentId = $meta->getReflectionProperty($identifierField)->getValue($parent); + $parentId = current($this->_em->getUnitOfWork()->getEntityIdentifier($parent)); $left = $meta->getReflectionProperty($config['left'])->getValue($node); $sign = $includeSelf ? '<=' : '<'; @@ -367,11 +359,10 @@ public function moveUp($node, $number = 1) * Removes given $node from the tree and reparents its descendants * * @param object $node - * @param bool $autoFlush - flush after removing * @throws RuntimeException - if something fails in transaction * @return void */ - public function removeFromTree($node, $autoFlush = true) + public function removeFromTree($node) { $meta = $this->getClassMetadata(); if ($node instanceof $meta->rootEntityName) { @@ -380,55 +371,85 @@ public function removeFromTree($node, $autoFlush = true) $left = $meta->getReflectionProperty($config['left'])->getValue($node); if ($right == $left + 1) { - $this->_em->remove($node); - $autoFlush && $this->_em->flush(); - return; + $this->removeSingle($node); + return; // node was a leaf } // process updates in transaction $this->_em->getConnection()->beginTransaction(); try { $parent = $meta->getReflectionProperty($config['parent'])->getValue($node); - if ($parent instanceof Proxy) { - $this->_em->refresh($parent); + $parentId = 'NULL'; + if ($parent) { + $parentId = current($this->_em->getUnitOfWork()->getEntityIdentifier($parent)); } $pk = $meta->getSingleIdentifierFieldName(); - $parentId = $meta->getReflectionProperty($pk)->getValue($parent); $nodeId = $meta->getReflectionProperty($pk)->getValue($node); - - $dql = "UPDATE {$meta->rootEntityName} node"; - $dql .= ' SET node.' . $config['parent'] . ' = ' . $parentId; - $dql .= ' WHERE node.' . $config['parent'] . ' = ' . $nodeId; $rootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null; - if (isset($config['root'])) { - $dql .= ' AND node.' . $config['root'] . ' = ' . $rootId; - } - $q = $this->_em->createQuery($dql); - $q->getSingleScalarResult(); - - $this->listener - ->getStrategy($this->_em, $meta->name) - ->shiftRangeRL($this->_em, $meta->rootEntityName, $left, $right, -1, $rootId, $rootId, - 1); + $shift = -1; + + // in case if root node is removed, childs become roots + if (isset($config['root']) && !$parent) { + $dql = "SELECT node.{$pk}, node.{$config['left']}, node.{$config['right']} FROM {$meta->rootEntityName} node"; + $dql .= " WHERE node.{$config['parent']} = {$nodeId}"; + $nodes = $this->_em->createQuery($dql)->getArrayResult(); + + foreach ($nodes as $newRoot) { + $left = $newRoot[$config['left']]; + $right = $newRoot[$config['right']]; + $rootId = $newRoot[$pk]; + $shift = -($left - 1); + + $dql = "UPDATE {$meta->rootEntityName} node"; + $dql .= ' SET node.' . $config['root'] . ' = :rootId'; + $dql .= ' WHERE node.' . $config['root'] . ' = :nodeId'; + $dql .= " AND node.{$config['left']} >= :left"; + $dql .= " AND node.{$config['right']} <= :right"; + + $q = $this->_em->createQuery($dql); + $q->setParameters(compact('rootId', 'left', 'right', 'nodeId')); + $q->getSingleScalarResult(); + + $dql = "UPDATE {$meta->rootEntityName} node"; + $dql .= ' SET node.' . $config['parent'] . ' = ' . $parentId; + $dql .= ' WHERE node.' . $config['parent'] . ' = ' . $nodeId; + $dql .= ' AND node.' . $config['root'] . ' = ' . $rootId; + + $q = $this->_em->createQuery($dql); + $q->getSingleScalarResult(); + + $this->listener + ->getStrategy($this->_em, $meta->name) + ->shiftRangeRL($this->_em, $meta->rootEntityName, $left, $right, $shift, $rootId, $rootId, - 1); + $this->listener + ->getStrategy($this->_em, $meta->name) + ->shiftRL($this->_em, $meta->rootEntityName, $right, -2, $rootId); + } + } else { + $dql = "UPDATE {$meta->rootEntityName} node"; + $dql .= ' SET node.' . $config['parent'] . ' = ' . $parentId; + $dql .= ' WHERE node.' . $config['parent'] . ' = ' . $nodeId; + if (isset($config['root'])) { + $dql .= ' AND node.' . $config['root'] . ' = ' . $rootId; + } + // @todo: update in memory nodes + $q = $this->_em->createQuery($dql); + $q->getSingleScalarResult(); - $this->listener - ->getStrategy($this->_em, $meta->name) - ->shiftRL($this->_em, $meta->rootEntityName, $right, -2, $rootId); + $this->listener + ->getStrategy($this->_em, $meta->name) + ->shiftRangeRL($this->_em, $meta->rootEntityName, $left, $right, $shift, $rootId, $rootId, - 1); - $dql = "UPDATE {$meta->rootEntityName} node"; - $dql .= ' SET node.' . $config['parent'] . ' = NULL,'; - $dql .= ' node.' . $config['left'] . ' = 0,'; - $dql .= ' node.' . $config['right'] . ' = 0'; - $dql .= ' WHERE node.' . $pk . ' = ' . $nodeId; - $q = $this->_em->createQuery($dql); - $q->getSingleScalarResult(); + $this->listener + ->getStrategy($this->_em, $meta->name) + ->shiftRL($this->_em, $meta->rootEntityName, $right, -2, $rootId); + } + $this->removeSingle($node); $this->_em->getConnection()->commit(); } catch (\Exception $e) { $this->_em->close(); $this->_em->getConnection()->rollback(); throw new \Gedmo\Exception\RuntimeException('Transaction failed', null, $e); } - $this->_em->refresh($node); - $this->_em->remove($node); - $autoFlush && $this->_em->flush(); } else { throw new InvalidArgumentException("Node is not related to this repository"); } @@ -456,7 +477,7 @@ public function reorder($node, $sortByField = null, $direction = 'ASC', $verify $nodes = $this->children($node, true, $sortByField, $direction); foreach ($nodes as $node) { // this is overhead but had to be refreshed - if ($node instanceof Proxy) { + if ($node instanceof Proxy && !$node->__isInitialized__) { $this->_em->refresh($node); } $right = $meta->getReflectionProperty($config['right'])->getValue($node); @@ -513,6 +534,7 @@ public function recover() if ($this->verify() === true) { return; } + // not yet implemented } /** @@ -609,7 +631,9 @@ private function verifyTree(&$errors, $root = null) } elseif ($right == $left) { $errors[] = "node [{$id}] has identical left and right values"; } elseif ($parent) { - $parent instanceof Proxy && $this->_em->refresh($parent); + if ($parent instanceof Proxy && !$parent->__isInitialized__) { + $this->_em->refresh($parent); + } $parentRight = $meta->getReflectionProperty($config['right'])->getValue($parent); $parentLeft = $meta->getReflectionProperty($config['left'])->getValue($parent); $parentId = $meta->getReflectionProperty($identifier)->getValue($parent); @@ -632,4 +656,35 @@ private function verifyTree(&$errors, $root = null) } } } + + /** + * Removes single node without touching children + * + * @internal + * @param object $node + * @return void + */ + private function removeSingle($node) + { + $meta = $this->getClassMetadata(); + $config = $this->listener->getConfiguration($this->_em, $meta->name); + + $pk = $meta->getSingleIdentifierFieldName(); + $nodeId = $meta->getReflectionProperty($pk)->getValue($node); + + // prevent from deleting whole branch + $dql = "UPDATE {$meta->rootEntityName} node"; + $dql .= ' SET node.' . $config['left'] . ' = 0,'; + $dql .= ' node.' . $config['right'] . ' = 0'; + $dql .= ' WHERE node.' . $pk . ' = ' . $nodeId; + $this->_em->createQuery($dql)->getSingleScalarResult(); + + // remove the node from database + $dql = "DELETE {$meta->rootEntityName} node"; + $dql .= " WHERE node.{$pk} = {$nodeId}"; + $this->_em->createQuery($dql)->getSingleScalarResult(); + + // remove from identity map + $this->_em->getUnitOfWork()->removeFromIdentityMap($node); + } } diff --git a/lib/Gedmo/Tree/Strategy/ORM/Nested.php b/lib/Gedmo/Tree/Strategy/ORM/Nested.php index 49bad457e2..e71542640d 100644 --- a/lib/Gedmo/Tree/Strategy/ORM/Nested.php +++ b/lib/Gedmo/Tree/Strategy/ORM/Nested.php @@ -2,9 +2,11 @@ namespace Gedmo\Tree\Strategy\ORM; +use Doctrine\ORM\Proxy\Proxy; use Gedmo\Tree\Strategy, Doctrine\ORM\EntityManager, Gedmo\Tree\TreeListener, + Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\ORM\Query; /** @@ -76,14 +78,6 @@ class Nested implements Strategy */ private $persistedNodes = array(); - /** - * Number of updates for specific - * classes to know if refresh is necessary - * - * @var array - */ - private $updatesOnNodeClasses = array(); - /** * {@inheritdoc} */ @@ -100,6 +94,37 @@ public function getName() return Strategy::NESTED; } + /** + * {@inheritdoc} + */ + public function processScheduledInsertion($em, $node) + { + $meta = $em->getClassMetadata(get_class($node)); + $config = $this->listener->getConfiguration($em, $meta->name); + + $parent = $meta->getReflectionProperty($config['parent'])->getValue($node); + if ($parent instanceof Proxy && !$parent->__isInitialized__) { + $em->refresh($parent); + } + if ($parent === null) { + $this->prepareRoot($em, $node); + if (isset($config['level'])) { + $meta->getReflectionProperty($config['level'])->setValue($node, 0); + } + } else { + $meta->getReflectionProperty($config['left'])->setValue($node, 0); + $meta->getReflectionProperty($config['right'])->setValue($node, 0); + if (isset($config['level'])) { + $meta->getReflectionProperty($config['level'])->setValue( + $node, + $meta->getReflectionProperty($config['level'])->getValue($parent) + 1 + ); + } + $this->pendingChildNodeInserts[$meta->name][] = $node; + } + $this->persistedNodes[spl_object_hash($node)] = null; + } + /** * {@inheritdoc} */ @@ -114,8 +139,6 @@ public function processScheduledUpdate($em, $node) throw new \Gedmo\Exception\UnexpectedValueException("Root cannot be changed manualy, change parent instead"); } if (isset($changeSet[$config['parent']])) { - $this->updatesOnNodeClasses[$meta->name] = isset($this->updatesOnNodeClasses[$meta->name]) ? - $this->updatesOnNodeClasses[$meta->name]+1 : 1; $this->updateNode($em, $node, $changeSet[$config['parent']][1]); } } @@ -130,6 +153,9 @@ public function processPostPersist($em, $node) if (isset($config['root'])) { $parent = $meta->getReflectionProperty($config['parent'])->getValue($node); + if ($parent instanceof Proxy && !$parent->__isInitialized__) { + $em->refresh($parent); + } $identifierField = $meta->getSingleIdentifierFieldName(); $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node); @@ -139,6 +165,8 @@ public function processPostPersist($em, $node) $rootId = $nodeId; } $meta->getReflectionProperty($config['root'])->setValue($node, $rootId); + $oid = spl_object_hash($node); + $em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['root'], $rootId); $dql = "UPDATE {$meta->rootEntityName} node"; $dql .= " SET node.{$config['root']} = {$rootId}"; $dql .= " WHERE node.{$identifierField} = {$nodeId}"; @@ -149,8 +177,8 @@ public function processPostPersist($em, $node) if (!$this->persistedNodes && $this->pendingChildNodeInserts) { $pendingChildNodeInserts = $this->pendingChildNodeInserts; foreach ($pendingChildNodeInserts as $class => &$nodes) { - while ($entity = array_shift($nodes)) { - $this->insertChild($em, $entity); + while ($node = array_shift($nodes)) { + $this->insertChild($em, $node); } } $this->pendingChildNodeInserts = array(); @@ -203,46 +231,18 @@ public function onFlushEnd($em) $this->updatesOnNodeClasses = array(); } - /** - * {@inheritdoc} - */ - public function processPrePersist($em, $node) - { - } - /** * {@inheritdoc} */ public function processPreRemove($em, $node) - { - } + {} /** * {@inheritdoc} */ - public function processScheduledInsertion($em, $node) + public function processPrePersist($em, $node) { - $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); - if (isset($config['level'])) { - $meta->getReflectionProperty($config['level'])->setValue($node, 0); - } - } else { - $meta->getReflectionProperty($config['left'])->setValue($node, 0); - $meta->getReflectionProperty($config['right'])->setValue($node, 0); - if (isset($config['level'])) { - $meta->getReflectionProperty($config['level'])->setValue( - $node, - $meta->getReflectionProperty($config['level'])->getValue($parent) + 1 - ); - } - $this->pendingChildNodeInserts[$meta->name][] = $node; - } - $this->persistedNodes[spl_object_hash($node)] = null; } /** @@ -251,20 +251,26 @@ public function processScheduledInsertion($em, $node) * * @param EntityManager $em * @param object $node + * @param string $position * @return void */ - public function insertChild(EntityManager $em, $node) + public function insertChild(EntityManager $em, $node, $position = 'firstChild') { $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) { + if ($parent instanceof Proxy && !$parent->__isInitialized__) { $em->refresh($parent); } + if (isset($config['level'])) { + $level = $parent ? ($meta->getReflectionProperty($config['level'])->getValue($parent) + 1) : 0; + $meta->getReflectionProperty($config['level'])->setValue($node, $level); + } + $identifierField = $meta->getSingleIdentifierFieldName(); + $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node); + $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); @@ -274,6 +280,9 @@ public function insertChild(EntityManager $em, $node) $dql = "UPDATE {$meta->rootEntityName} node"; $dql .= " SET node.{$config['left']} = " . ($parentRight) . ', '; $dql .= " node.{$config['right']} = " . ($parentRight + 1); + if (isset($level)) { + $dql .= ", node.{$config['level']} = " . $level; + } $dql .= " WHERE node.{$identifierField} = {$nodeId}"; $em->createQuery($dql)->getSingleScalarResult(); } @@ -295,10 +304,6 @@ public function updateNode(EntityManager $em, $node, $parent, $position = 'first $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); @@ -310,9 +315,10 @@ public function updateNode(EntityManager $em, $node, $parent, $position = 'first $treeSize = $right - $left + 1; $newRootId = null; if ($parent) { - if (!isset($this->updatesOnNodeClasses[$meta->name]) || $this->updatesOnNodeClasses[$meta->name] > 1) { + if ($parent instanceof Proxy && !$parent->__isInitialized__) { $em->refresh($parent); } + $parentRootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($parent) : null; $parentLeft = $meta->getReflectionProperty($config['left'])->getValue($parent); $parentRight = $meta->getReflectionProperty($config['right'])->getValue($parent); @@ -359,15 +365,15 @@ public function updateNode(EntityManager $em, $node, $parent, $position = 'first } $diff = $start - $left; - $qb = $em->createQueryBuilder(); + /*$qb = $em->createQueryBuilder(); $qb->update($meta->rootEntityName, 'node'); if (isset($config['root'])) { - $qb->set('node.' . $config['root'], $newRootId); + //$qb->set('node.' . $config['root'], $newRootId); } if (isset($config['level'])) { $qb->set('node.' . $config['level'], $level); } - if ($treeSize > 2) { + if ($treeSize > 2 && !$isNewNode) {*/ $levelDiff = isset($config['level']) ? $level - $meta->getReflectionProperty($config['level'])->getValue($node) : null; $this->shiftRangeRL( $em, @@ -379,14 +385,13 @@ public function updateNode(EntityManager $em, $node, $parent, $position = 'first $newRootId, $levelDiff ); - } else { + $this->shiftRL($em, $meta->rootEntityName, $left, -$treeSize, $rootId); + /*} else { $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); + $qb->where("node.{$identifierField} = {$nodeId}"); + $qb->getQuery()->getSingleScalarResult(); + }*/ } /** @@ -428,10 +433,10 @@ public function shiftRL(EntityManager $em, $class, $first, $delta, $rootId = nul $config = $this->listener->getConfiguration($em, $class); $sign = ($delta >= 0) ? ' + ' : ' - '; - $delta = abs($delta); + $absDelta = abs($delta); $dql = "UPDATE {$meta->rootEntityName} node"; - $dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$delta}"; + $dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$absDelta}"; $dql .= " WHERE node.{$config['left']} >= {$first}"; if (isset($config['root'])) { $dql .= " AND node.{$config['root']} = {$rootId}"; @@ -440,13 +445,39 @@ public function shiftRL(EntityManager $em, $class, $first, $delta, $rootId = nul $q->getSingleScalarResult(); $dql = "UPDATE {$meta->rootEntityName} node"; - $dql .= " SET node.{$config['right']} = node.{$config['right']} {$sign} {$delta}"; + $dql .= " SET node.{$config['right']} = node.{$config['right']} {$sign} {$absDelta}"; $dql .= " WHERE node.{$config['right']} >= {$first}"; if (isset($config['root'])) { $dql .= " AND node.{$config['root']} = {$rootId}"; } $q = $em->createQuery($dql); $q->getSingleScalarResult(); + // update in memory nodes increases performance, saves some IO + foreach ($em->getUnitOfWork()->getIdentityMap() as $className => $nodes) { + if ($className !== $meta->name && !$this->inDistriminatorMap($meta, $className)) { + continue; + } + if ($className !== $meta->name) { + $meta = $em->getClassMetadata($className); + } + foreach ($nodes as $node) { + if ($node instanceof Proxy && !$node->__isInitialized__) { + continue; + } + $oid = spl_object_hash($node); + $left = $meta->getReflectionProperty($config['left'])->getValue($node); + $root = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null; + if ($root === $rootId && $left >= $first) { + $meta->getReflectionProperty($config['left'])->setValue($node, $left + $delta); + $em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['left'], $left + $delta); + } + $right = $meta->getReflectionProperty($config['right'])->getValue($node); + if ($root === $rootId && $right >= $first) { + $meta->getReflectionProperty($config['right'])->setValue($node, $right + $delta); + $em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['right'], $right + $delta); + } + } + } } /** @@ -469,18 +500,18 @@ public function shiftRangeRL(EntityManager $em, $class, $first, $last, $delta, $ $config = $this->listener->getConfiguration($em, $class); $sign = ($delta >= 0) ? ' + ' : ' - '; - $delta = abs($delta); + $absDelta = abs($delta); $levelSign = ($levelDelta >= 0) ? ' + ' : ' - '; - $levelDelta = abs($levelDelta); + $absLevelDelta = 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}"; + $dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$absDelta}"; + $dql .= ", node.{$config['right']} = node.{$config['right']} {$sign} {$absDelta}"; if (isset($config['root'])) { $dql .= ", node.{$config['root']} = {$destRootId}"; } if (isset($config['level'])) { - $dql .= ", node.{$config['level']} = node.{$config['level']} {$levelSign} {$levelDelta}"; + $dql .= ", node.{$config['level']} = node.{$config['level']} {$levelSign} {$absLevelDelta}"; } $dql .= " WHERE node.{$config['left']} >= {$first}"; $dql .= " AND node.{$config['right']} <= {$last}"; @@ -489,6 +520,58 @@ public function shiftRangeRL(EntityManager $em, $class, $first, $last, $delta, $ } $q = $em->createQuery($dql); $q->getSingleScalarResult(); + // update in memory nodes increases performance, saves some IO + foreach ($em->getUnitOfWork()->getIdentityMap() as $className => $nodes) { + if ($className !== $meta->name && !$this->inDistriminatorMap($meta, $className)) { + continue; + } + if ($className !== $meta->name) { + $meta = $em->getClassMetadata($className); + } + foreach ($nodes as $node) { + if ($node instanceof Proxy && !$node->__isInitialized__) { + continue; + } + $left = $meta->getReflectionProperty($config['left'])->getValue($node); + $right = $meta->getReflectionProperty($config['right'])->getValue($node); + $root = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null; + if ($root === $rootId && $left >= $first && $right <= $last) { + $oid = spl_object_hash($node); + $uow = $em->getUnitOfWork(); + + $meta->getReflectionProperty($config['left'])->setValue($node, $left + $delta); + $uow->setOriginalEntityProperty($oid, $config['left'], $left + $delta); + $meta->getReflectionProperty($config['right'])->setValue($node, $right + $delta); + $uow->setOriginalEntityProperty($oid, $config['right'], $right + $delta); + if (isset($config['root'])) { + $meta->getReflectionProperty($config['root'])->setValue($node, $destRootId); + $uow->setOriginalEntityProperty($oid, $config['root'], $destRootId); + } + if (isset($config['level'])) { + $level = $meta->getReflectionProperty($config['level'])->getValue($node); + $meta->getReflectionProperty($config['level'])->setValue($node, $level + $levelDelta); + $uow->setOriginalEntityProperty($oid, $config['level'], $level + $levelDelta); + } + } + } + } + } + + /** + * Check if given class is in inheritance discriminator map + * + * @param ClassMetadataInfo $meta + * @param string $className + * @return boolean + */ + public function inDistriminatorMap(ClassMetadataInfo $meta, $className) + { + foreach ($meta->discriminatorMap as $class) { + if ($className === $class) { + return true; + } + } + return false; } /** @@ -509,7 +592,7 @@ private function prepareRoot(EntityManager $em, $node) $meta->getReflectionProperty($config['right'])->setValue($node, 2); } else { $edge = isset($this->treeEdges[$meta->name]) ? - $this->treeEdges[$meta->name] : $this->max($em, $meta->rootEntityName); + $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); $this->treeEdges[$meta->name] = $edge + 2; diff --git a/tests/Gedmo/Tree/Fixture/BaseNode.php b/tests/Gedmo/Tree/Fixture/BaseNode.php index 78b5bf2a8f..215575821e 100644 --- a/tests/Gedmo/Tree/Fixture/BaseNode.php +++ b/tests/Gedmo/Tree/Fixture/BaseNode.php @@ -14,56 +14,59 @@ class BaseNode extends ANode /** * @gedmo:TreeParent * @ManyToOne(targetEntity="BaseNode", inversedBy="children") + * @JoinColumns({ + * @JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL") + * }) */ private $parent; - + /** * @OneToMany(targetEntity="BaseNode", mappedBy="parent") */ private $children; - + /** * @gedmo:Timestampable(on="create") * @Column(type="datetime") */ private $created; - + /** * @Column(length=128, unique=true) */ private $identifier; - + /** * @Column(type="datetime") * @gedmo:Timestampable */ private $updated; - + public function getCreated() { return $this->created; } - + public function getUpdated() { return $this->updated; } - + public function setParent($parent = null) { - $this->parent = $parent; + $this->parent = $parent; } - + public function getChildren() { return $this->children; } - + public function getParent() { - return $this->parent; + return $this->parent; } - + public function getIdentifier() { return $this->identifier; diff --git a/tests/Gedmo/Tree/Fixture/BehavioralCategory.php b/tests/Gedmo/Tree/Fixture/BehavioralCategory.php index c358721ea2..04f11d9d8e 100644 --- a/tests/Gedmo/Tree/Fixture/BehavioralCategory.php +++ b/tests/Gedmo/Tree/Fixture/BehavioralCategory.php @@ -27,24 +27,27 @@ class BehavioralCategory * @Column(name="lft", type="integer", nullable=true) */ private $lft; - + /** * @gedmo:TreeRight * @Column(name="rgt", type="integer", nullable=true) */ private $rgt; - + /** * @gedmo:TreeParent * @ManyToOne(targetEntity="BehavioralCategory", inversedBy="children") + * @JoinColumns({ + * @JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL") + * }) */ private $parent; - + /** * @OneToMany(targetEntity="BehavioralCategory", mappedBy="parent") */ private $children; - + /** * @gedmo:Translatable * @gedmo:Slug @@ -56,12 +59,12 @@ public function getId() { return $this->id; } - + public function getSlug() { return $this->slug; } - + public function setTitle($title) { $this->title = $title; @@ -71,14 +74,14 @@ public function getTitle() { return $this->title; } - + public function setParent(BehavioralCategory $parent) { - $this->parent = $parent; + $this->parent = $parent; } - + public function getParent() { - return $this->parent; + return $this->parent; } } diff --git a/tests/Gedmo/Tree/Fixture/Category.php b/tests/Gedmo/Tree/Fixture/Category.php index 691ff6be08..40a793a593 100644 --- a/tests/Gedmo/Tree/Fixture/Category.php +++ b/tests/Gedmo/Tree/Fixture/Category.php @@ -27,30 +27,33 @@ class Category implements NodeInterface * @Column(name="lft", type="integer") */ private $lft; - + /** * @gedmo:TreeRight * @Column(name="rgt", type="integer") */ private $rgt; - + /** * @gedmo:TreeParent * @ManyToOne(targetEntity="Category", inversedBy="children") + * @JoinColumns({ + * @JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL") + * }) */ private $parentId; - + /** * @gedmo:TreeLevel * @Column(name="lvl", type="integer") */ private $level; - + /** * @OneToMany(targetEntity="Category", mappedBy="parent") */ private $children; - + /** * @OneToMany(targetEntity="Article", mappedBy="category") */ @@ -60,7 +63,7 @@ public function getId() { return $this->id; } - + public function setTitle($title) { $this->title = $title; @@ -70,14 +73,14 @@ public function getTitle() { return $this->title; } - + public function setParent(Category $parent) { - $this->parentId = $parent; + $this->parentId = $parent; } - + public function getParent() { - return $this->parentId; + return $this->parentId; } } diff --git a/tests/Gedmo/Tree/Fixture/RootCategory.php b/tests/Gedmo/Tree/Fixture/RootCategory.php index 19b62b5aeb..e232587dc5 100644 --- a/tests/Gedmo/Tree/Fixture/RootCategory.php +++ b/tests/Gedmo/Tree/Fixture/RootCategory.php @@ -25,31 +25,34 @@ class RootCategory * @Column(name="lft", type="integer") */ private $lft; - + /** * @gedmo:TreeRight * @Column(name="rgt", type="integer") */ private $rgt; - + /** * @gedmo:TreeParent * @ManyToOne(targetEntity="RootCategory", inversedBy="children") + * @JoinColumns({ + * @JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL") + * }) */ private $parent; - + /** * @gedmo:TreeRoot * @Column(type="integer", nullable=true) */ private $root; - + /** * @gedmo:TreeLevel * @Column(name="lvl", type="integer") */ private $level; - + /** * @OneToMany(targetEntity="RootCategory", mappedBy="parent") */ @@ -59,7 +62,7 @@ public function getId() { return $this->id; } - + public function setTitle($title) { $this->title = $title; @@ -69,32 +72,32 @@ public function getTitle() { return $this->title; } - + public function setParent(RootCategory $parent = null) { - $this->parent = $parent; + $this->parent = $parent; } - + public function getParent() { - return $this->parent; + return $this->parent; } - + public function getRoot() { - return $this->root; + return $this->root; } - + public function getLeft() { return $this->lft; } - + public function getRight() { return $this->rgt; } - + public function getLevel() { return $this->level; diff --git a/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php b/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php index eb1e7db88a..f00c4a3725 100644 --- a/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php +++ b/tests/Gedmo/Tree/NestedTreeRootRepositoryTest.php @@ -30,6 +30,33 @@ protected function setUp() $this->populate(); } + public function testRootRemoval() + { + $repo = $this->em->getRepository(self::CATEGORY); + $this->populateMore(); + + $food = $repo->findOneByTitle('Food'); + $repo->removeFromTree($food); + $this->em->clear(); + + $food = $repo->findOneByTitle('Food'); + $this->assertTrue(is_null($food)); + + $node = $repo->findOneByTitle('Fruits'); + + $this->assertEquals(1, $node->getLeft()); + $this->assertEquals(2, $node->getRight()); + $this->assertEquals(3, $node->getRoot()); + $this->assertTrue(is_null($node->getParent())); + + $node = $repo->findOneByTitle('Vegitables'); + + $this->assertEquals(1, $node->getLeft()); + $this->assertEquals(10, $node->getRight()); + $this->assertEquals(4, $node->getRoot()); + $this->assertTrue(is_null($node->getParent())); + } + public function testRepository() { $repo = $this->em->getRepository(self::CATEGORY); @@ -82,10 +109,10 @@ public function testAdvancedRepositoryFunctions() $this->em->createQuery($dql)->getSingleScalarResult(); //@todo implement - /*$this->em->clear(); - $repo->recover(); - $this->em->clear(); - $this->assertTrue($repo->verify());*/ + //$this->em->clear(); + //$repo->recover(); + //$this->em->clear(); + //$this->assertTrue($repo->verify()); $this->em->clear(); $onions = $repo->findOneByTitle('Onions'); @@ -96,13 +123,11 @@ public function testAdvancedRepositoryFunctions() // move up $repo->moveUp($onions); - $this->em->refresh($onions); $this->assertEquals(9, $onions->getLeft()); $this->assertEquals(10, $onions->getRight()); $repo->moveUp($onions, true); - $this->em->refresh($onions); $this->assertEquals(5, $onions->getLeft()); $this->assertEquals(6, $onions->getRight()); @@ -110,19 +135,15 @@ public function testAdvancedRepositoryFunctions() // move down $repo->moveDown($onions, 2); - $this->em->refresh($onions); $this->assertEquals(9, $onions->getLeft()); $this->assertEquals(10, $onions->getRight()); // reorder - $this->em->clear(); $node = $repo->findOneByTitle('Food'); $repo->reorder($node, 'title', 'ASC', false); - $this->em->clear(); - $node = $repo->findOneByTitle('Cabbages'); $this->assertEquals(5, $node->getLeft()); @@ -155,7 +176,6 @@ public function testAdvancedRepositoryFunctions() // remove - $this->em->clear(); $node = $repo->findOneByTitle('Fruits'); $id = $node->getId(); $repo->removeFromTree($node); @@ -198,7 +218,6 @@ private function populateMore() $this->em->persist($cabbages); $this->em->persist($onions); $this->em->flush(); - $this->em->clear(); } private function populate() @@ -232,6 +251,5 @@ private function populate() $this->em->persist($childsChild); $this->em->persist($potatoes); $this->em->flush(); - $this->em->clear(); } } diff --git a/tests/Gedmo/Tree/RepositoryTest.php b/tests/Gedmo/Tree/RepositoryTest.php index b9fb2cc153..4cc0629103 100644 --- a/tests/Gedmo/Tree/RepositoryTest.php +++ b/tests/Gedmo/Tree/RepositoryTest.php @@ -135,7 +135,6 @@ public function testAdvancedFunctions() // move up onions by one position $repo->moveUp($onions, 1); - $this->em->refresh($onions); $left = $meta->getReflectionProperty('lft')->getValue($onions); $right = $meta->getReflectionProperty('rgt')->getValue($onions); @@ -145,7 +144,6 @@ public function testAdvancedFunctions() // move down onions by one position $repo->moveDown($onions, 1); - $this->em->refresh($onions); $left = $meta->getReflectionProperty('lft')->getValue($onions); $right = $meta->getReflectionProperty('rgt')->getValue($onions); @@ -156,7 +154,6 @@ public function testAdvancedFunctions() // move to the up onions on this level $repo->moveUp($onions, true); - $this->em->refresh($onions); $left = $meta->getReflectionProperty('lft')->getValue($onions); $right = $meta->getReflectionProperty('rgt')->getValue($onions); @@ -165,14 +162,10 @@ public function testAdvancedFunctions() $this->assertEquals($right, 6); // test tree reordering - - $this->em->clear(); // clear all cached nodes - // reorder tree by title $food = $repo->findOneByTitle('Food'); $repo->reorder($food, 'title'); - $this->em->clear(); // clear all cached nodes $node = $this->em->getRepository(self::CATEGORY) ->findOneByTitle('Cabbages'); @@ -208,7 +201,6 @@ public function testAdvancedFunctions() // test removal with reparenting - $this->em->clear(); // clear all cached nodes $vegies = $this->em->getRepository(self::CATEGORY) ->findOneByTitle('Vegitables'); @@ -240,6 +232,36 @@ public function testAdvancedFunctions() $this->assertEquals('Food', $node->getParent()->getTitle()); } + public function testRootRemoval() + { + $repo = $this->em->getRepository(self::CATEGORY); + $meta = $this->em->getClassMetadata(self::CATEGORY); + $this->populateMore(); + + $food = $repo->findOneByTitle('Food'); + $repo->removeFromTree($food); + $this->em->clear(); + + $food = $repo->findOneByTitle('Food'); + $this->assertTrue(is_null($food)); + + $node = $repo->findOneByTitle('Fruits'); + $left = $meta->getReflectionProperty('lft')->getValue($node); + $right = $meta->getReflectionProperty('rgt')->getValue($node); + + $this->assertEquals($left, 1); + $this->assertEquals($right, 2); + $this->assertTrue(is_null($node->getParent())); + + $node = $repo->findOneByTitle('Vegitables'); + $left = $meta->getReflectionProperty('lft')->getValue($node); + $right = $meta->getReflectionProperty('rgt')->getValue($node); + + $this->assertEquals($left, 3); + $this->assertEquals($right, 12); + $this->assertTrue(is_null($node->getParent())); + } + public function testVerificationAndRecover() { $repo = $this->em->getRepository(self::CATEGORY); diff --git a/tests/Gedmo/Tree/TreeTest.php b/tests/Gedmo/Tree/TreeTest.php index 778fa11874..cb6423f8e1 100644 --- a/tests/Gedmo/Tree/TreeTest.php +++ b/tests/Gedmo/Tree/TreeTest.php @@ -159,6 +159,7 @@ public function testTheTree() $this->em->persist($yetAnotherChild); $yetAnotherChild->setTitle("yetanotherchild"); $yetAnotherChild->setParent($root); + //$this->em->persist($yetAnotherChild); $this->em->flush(); $this->em->clear(); @@ -171,6 +172,54 @@ public function testTheTree() $this->assertEquals($level, 1); } + public function testIssue33() + { + $repo = $this->em->getRepository(self::CATEGORY); + + $root = new Category; + $root->setTitle('root'); + + $node1 = new Category; + $node1->setTitle('node1'); + $node1->setParent($root); + + $node2 = new Category; + $node2->setTitle('node2'); + $node2->setParent($root); + + $subNode = new Category; + $subNode->setTitle('sub-node'); + $subNode->setParent($node2); + + $this->em->persist($root); + $this->em->persist($node1); + $this->em->persist($node2); + $this->em->persist($subNode); + $this->em->flush(); + $this->em->clear(); + + $subNode = $repo->findOneByTitle('sub-node'); + $node1 = $repo->findOneByTitle('node1'); + $subNode->setParent($node1); + + $this->em->persist($subNode); + $this->em->flush(); + $this->em->clear(); + + $meta = $this->em->getClassMetadata(self::CATEGORY); + $subNode = $repo->findOneByTitle('sub-node'); + $left = $meta->getReflectionProperty('lft')->getValue($subNode); + $right = $meta->getReflectionProperty('rgt')->getValue($subNode); + $this->assertEquals($left, 3); + $this->assertEquals($right, 4); + + $node1 = $repo->findOneByTitle('node1'); + $left = $meta->getReflectionProperty('lft')->getValue($node1); + $right = $meta->getReflectionProperty('rgt')->getValue($node1); + $this->assertEquals($left, 2); + $this->assertEquals($right, 5); + } + protected function getUsedEntityFixtures() { return array(