Closure.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. namespace Gedmo\Tree\Strategy\ORM;
  3. use Gedmo\Tree\Strategy,
  4. Doctrine\ORM\EntityManager,
  5. Doctrine\ORM\Proxy\Proxy,
  6. Gedmo\Tree\AbstractTreeListener;
  7. /**
  8. * This strategy makes tree act like
  9. * a closure table.
  10. *
  11. * Some Tree logic is copied from -
  12. * CakePHP: Rapid Development Framework (http://cakephp.org)
  13. *
  14. * @author Gustavo Adrian <comfortablynumb84@gmail.com>
  15. * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  16. * @package Gedmo.Tree.Strategy.ORM
  17. * @subpackage Closure
  18. * @link http://www.gediminasm.org
  19. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  20. */
  21. class Closure implements Strategy
  22. {
  23. /**
  24. * TreeListener
  25. *
  26. * @var AbstractTreeListener
  27. */
  28. protected $listener = null;
  29. /**
  30. * List of pending Nodes, which needs to
  31. * be post processed because of having a parent Node
  32. * which requires some additional calculations
  33. *
  34. * @var array
  35. */
  36. protected $pendingChildNodeInserts = array();
  37. /**
  38. * List of pending Nodes to remove
  39. *
  40. * @var array
  41. */
  42. protected $pendingNodesForRemove = array();
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public function __construct(AbstractTreeListener $listener)
  47. {
  48. $this->listener = $listener;
  49. }
  50. /**
  51. * {@inheritdoc}
  52. */
  53. public function getName()
  54. {
  55. return Strategy::CLOSURE;
  56. }
  57. /**
  58. * {@inheritdoc}
  59. */
  60. public function processPrePersist($em, $entity)
  61. {
  62. $this->pendingChildNodeInserts[] = $entity;
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function processPostPersist( $em, $entity )
  68. {
  69. if ( count( $this->pendingChildNodeInserts ) )
  70. {
  71. while ( $entity = array_shift( $this->pendingChildNodeInserts ) )
  72. {
  73. $this->insertNode( $em, $entity );
  74. }
  75. }
  76. }
  77. public function insertNode(EntityManager $em, $entity, $addNodeChildrenToAncestors = false )
  78. {
  79. $meta = $em->getClassMetadata(get_class($entity));
  80. $config = $this->listener->getConfiguration($em, $meta->name);
  81. $identifier = $meta->getSingleIdentifierFieldName();
  82. $id = $meta->getReflectionProperty($identifier)->getValue($entity);
  83. $closureMeta = $em->getClassMetadata($config['closure']);
  84. $entries = array();
  85. // If node has children it means it already has a self referencing row, so we skip its insertion
  86. if ( $addNodeChildrenToAncestors === false )
  87. {
  88. $entries[] = array(
  89. 'ancestor' => $id,
  90. 'descendant' => $id,
  91. 'depth' => 0
  92. );
  93. }
  94. $parent = $meta->getReflectionProperty( $config[ 'parent' ] )->getValue( $entity );
  95. if ( $parent )
  96. {
  97. $parentId = $meta->getReflectionProperty($identifier)->getValue($parent);
  98. $dql = "SELECT c.ancestor, c.depth FROM {$closureMeta->name} c";
  99. $dql .= " WHERE c.descendant = {$parentId}";
  100. $ancestors = $em->createQuery($dql)->getArrayResult();
  101. //echo count($ancestors);
  102. foreach ($ancestors as $ancestor) {
  103. $entries[] = array(
  104. 'ancestor' => $ancestor['ancestor'],
  105. 'descendant' => $id,
  106. 'depth' => $ancestor['depth'] + 1
  107. );
  108. if ( $addNodeChildrenToAncestors === true )
  109. {
  110. $dql = "SELECT c.descendant, c.depth FROM {$closureMeta->name} c";
  111. $dql .= " WHERE c.ancestor = {$id} AND c.ancestor != c.descendant";
  112. $children = $em->createQuery( $dql )
  113. ->getArrayResult();
  114. foreach ( $children as $child )
  115. {
  116. $entries[] = array(
  117. 'ancestor' => $ancestor[ 'ancestor' ],
  118. 'descendant' => $child[ 'descendant' ],
  119. 'depth' => $child[ 'depth' ] + 1
  120. );
  121. }
  122. }
  123. }
  124. }
  125. $table = $closureMeta->getTableName();
  126. foreach ($entries as $closure) {
  127. if (!$em->getConnection()->insert($table, $closure)) {
  128. throw new \Gedmo\Exception\RuntimeException('Failed to insert new Closure record');
  129. }
  130. }
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function processScheduledUpdate($em, $entity)
  136. {
  137. $entityClass = get_class($entity);
  138. $config = $this->listener->getConfiguration($em, $entityClass);
  139. $meta = $em->getClassMetadata($entityClass);
  140. $uow = $em->getUnitOfWork();
  141. $changeSet = $uow->getEntityChangeSet($entity);
  142. if (array_key_exists($config['parent'], $changeSet)) {
  143. $this->updateNode($em, $entity, $changeSet[$config['parent']]);
  144. }
  145. }
  146. public function updateNode(EntityManager $em, $entity, array $change)
  147. {
  148. $meta = $em->getClassMetadata(get_class($entity));
  149. $config = $this->listener->getConfiguration($em, $meta->name);
  150. $closureMeta = $em->getClassMetadata($config['closure']);
  151. $oldParent = $change[ 0 ];
  152. $nodeId = $this->extractIdentifier($em, $entity);
  153. $table = $closureMeta->getTableName();
  154. if ( $oldParent )
  155. {
  156. $this->removeClosurePathsOfNodeID( $em, $table, $nodeId );
  157. $this->insertNode( $em, $entity, true );
  158. }
  159. //\Doctrine\Common\Util\Debug::dump($oldParent);
  160. //die();
  161. }
  162. /**
  163. * {@inheritdoc}
  164. */
  165. public function processScheduledDelete( $em, $entity )
  166. {
  167. $this->removeNode( $em, $entity );
  168. }
  169. public function removeNode( EntityManager $em, $entity, $maintainSelfReferencingRow = false, $maintainSelfReferencingRowOfChildren = false )
  170. {
  171. $meta = $em->getClassMetadata( get_class( $entity ) );
  172. $config = $this->listener->getConfiguration( $em, $meta->name );
  173. $closureMeta = $em->getClassMetadata( $config[ 'closure' ] );
  174. $this->removeClosurePathsOfNodeID( $em, $closureMeta->getTableName(), $entity->getId(), $maintainSelfReferencingRow, $maintainSelfReferencingRowOfChildren );
  175. }
  176. public function removeClosurePathsOfNodeID( EntityManager $em, $table, $nodeId, $maintainSelfReferencingRow = true, $maintainSelfReferencingRowOfChildren = true )
  177. {
  178. $subquery = "SELECT c1.id FROM {$table} c1 ";
  179. $subquery .= "WHERE c1.descendant IN ( SELECT c2.descendant FROM {$table} c2 WHERE c2.ancestor = :id ) ";
  180. $subquery .= "AND ( c1.ancestor IN ( SELECT c3.ancestor FROM {$table} c3 WHERE c3.descendant = :id ";
  181. if ( $maintainSelfReferencingRow === true )
  182. {
  183. $subquery .= "AND c3.descendant != c3.ancestor ";
  184. }
  185. if ( $maintainSelfReferencingRowOfChildren === false )
  186. {
  187. $subquery .= " OR c1.descendant = c1.ancestor ";
  188. }
  189. $subquery .= " ) ) ";
  190. $subquery = "DELETE FROM {$table} WHERE {$table}.id IN ( SELECT temp_table.id FROM ( {$subquery} ) temp_table )";
  191. if (!$em->getConnection()->executeQuery($subquery, array('id' => $nodeId))) {
  192. throw new \Gedmo\Exception\RuntimeException('Failed to delete old Closure records');
  193. }
  194. }
  195. private function extractIdentifier($em, $entity, $single = true)
  196. {
  197. if ($entity instanceof Proxy) {
  198. $id = $em->getUnitOfWork()->getEntityIdentifier($entity);
  199. } else {
  200. $meta = $em->getClassMetadata(get_class($entity));
  201. $id = array();
  202. foreach ($meta->identifier as $name) {
  203. $id[$name] = $meta->getReflectionProperty($name)->getValue($entity);
  204. }
  205. }
  206. if ($single) {
  207. $id = current($id);
  208. }
  209. return $id;
  210. }
  211. /**
  212. * {@inheritdoc}
  213. */
  214. public function onFlushEnd($em)
  215. {}
  216. }