Closure.php 8.0 KB

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