* @author Gediminas Morkevicius * @package Gedmo.Tree.Strategy.ORM * @subpackage Closure * @link http://www.gediminasm.org * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ class Closure implements Strategy { /** * TreeListener * * @var AbstractTreeListener */ protected $listener = null; /** * List of pending Nodes, which needs to * be post processed because of having a parent Node * which requires some additional calculations * * @var array */ protected $pendingChildNodeInserts = array(); /** * List of pending Nodes to remove * * @var array */ protected $pendingNodesForRemove = array(); /** * {@inheritdoc} */ public function __construct(AbstractTreeListener $listener) { $this->listener = $listener; } /** * {@inheritdoc} */ public function getName() { return Strategy::CLOSURE; } /** * {@inheritdoc} */ public function processPrePersist($em, $entity) { $this->pendingChildNodeInserts[] = $entity; } /** * {@inheritdoc} */ public function processPostPersist($em, $entity) { if (count($this->pendingChildNodeInserts)) { while ($entity = array_shift($this->pendingChildNodeInserts)) { $this->insertNode($em, $entity); } } } public function insertNode(EntityManager $em, $entity, $addNodeChildrenToAncestors = false) { $meta = $em->getClassMetadata(get_class($entity)); $config = $this->listener->getConfiguration($em, $meta->name); $identifier = $meta->getSingleIdentifierFieldName(); $id = $meta->getReflectionProperty($identifier)->getValue($entity); $closureMeta = $em->getClassMetadata($config['closure']); $entries = array(); // If node has children it means it already has a self referencing row, so we skip its insertion if ($addNodeChildrenToAncestors === false) { $entries[] = array( 'ancestor' => $id, 'descendant' => $id, 'depth' => 0 ); } $parent = $meta->getReflectionProperty($config['parent'])->getValue($entity); if ( $parent ) { $parentId = $meta->getReflectionProperty($identifier)->getValue($parent); $dql = "SELECT c.ancestor, c.depth FROM {$closureMeta->name} c"; $dql .= " WHERE c.descendant = {$parentId}"; $ancestors = $em->createQuery($dql)->getArrayResult(); //echo count($ancestors); foreach ($ancestors as $ancestor) { $entries[] = array( 'ancestor' => $ancestor['ancestor'], 'descendant' => $id, 'depth' => $ancestor['depth'] + 1 ); if ($addNodeChildrenToAncestors === true) { $dql = "SELECT c.descendant, c.depth FROM {$closureMeta->name} c"; $dql .= " WHERE c.ancestor = {$id} AND c.ancestor != c.descendant"; $children = $em->createQuery($dql) ->getArrayResult(); foreach ($children as $child) { $entries[] = array( 'ancestor' => $ancestor['ancestor'], 'descendant' => $child['descendant'], 'depth' => $child['depth'] + 1 ); } } } } $table = $closureMeta->getTableName(); foreach ($entries as $closure) { if (!$em->getConnection()->insert($table, $closure)) { throw new \Gedmo\Exception\RuntimeException('Failed to insert new Closure record'); } } } /** * {@inheritdoc} */ public function processScheduledUpdate($em, $entity) { $entityClass = get_class($entity); $config = $this->listener->getConfiguration($em, $entityClass); $meta = $em->getClassMetadata($entityClass); $uow = $em->getUnitOfWork(); $changeSet = $uow->getEntityChangeSet($entity); if (array_key_exists($config['parent'], $changeSet)) { $this->updateNode($em, $entity, $changeSet[$config['parent']]); } } public function updateNode(EntityManager $em, $entity, array $change) { $meta = $em->getClassMetadata(get_class($entity)); $config = $this->listener->getConfiguration($em, $meta->name); $closureMeta = $em->getClassMetadata($config['closure']); $oldParent = $change[0]; $nodeId = $this->extractIdentifier($em, $entity); $table = $closureMeta->getTableName(); if ($oldParent) { $this->removeClosurePathsOfNodeID($em, $table, $nodeId); $this->insertNode($em, $entity, true); } //\Doctrine\Common\Util\Debug::dump($oldParent); //die(); } /** * {@inheritdoc} */ public function processScheduledDelete($em, $entity) { $this->removeNode($em, $entity); } public function removeNode(EntityManager $em, $entity, $maintainSelfReferencingRow = false, $maintainSelfReferencingRowOfChildren = false) { $meta = $em->getClassMetadata(get_class($entity)); $config = $this->listener->getConfiguration($em, $meta->name); $closureMeta = $em->getClassMetadata($config['closure']); $this->removeClosurePathsOfNodeID($em, $closureMeta->getTableName(), $entity->getId(), $maintainSelfReferencingRow, $maintainSelfReferencingRowOfChildren); } public function removeClosurePathsOfNodeID(EntityManager $em, $table, $nodeId, $maintainSelfReferencingRow = true, $maintainSelfReferencingRowOfChildren = true) { $subquery = "SELECT c1.id FROM {$table} c1 "; $subquery .= "WHERE c1.descendant IN ( SELECT c2.descendant FROM {$table} c2 WHERE c2.ancestor = :id ) "; $subquery .= "AND ( c1.ancestor IN ( SELECT c3.ancestor FROM {$table} c3 WHERE c3.descendant = :id "; if ($maintainSelfReferencingRow === true) { $subquery .= "AND c3.descendant != c3.ancestor "; } if ( $maintainSelfReferencingRowOfChildren === false ) { $subquery .= " OR c1.descendant = c1.ancestor "; } $subquery .= " ) ) "; $subquery = "DELETE FROM {$table} WHERE {$table}.id IN ( SELECT temp_table.id FROM ( {$subquery} ) temp_table )"; if (!$em->getConnection()->executeQuery($subquery, array('id' => $nodeId))) { throw new \Gedmo\Exception\RuntimeException('Failed to delete old Closure records'); } } private function extractIdentifier($em, $entity, $single = true) { if ($entity instanceof Proxy) { $id = $em->getUnitOfWork()->getEntityIdentifier($entity); } else { $meta = $em->getClassMetadata(get_class($entity)); $id = array(); foreach ($meta->identifier as $name) { $id[$name] = $meta->getReflectionProperty($name)->getValue($entity); } } if ($single) { $id = current($id); } return $id; } /** * {@inheritdoc} */ public function onFlushEnd($em) {} }