Closure.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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. $meta = $em->getClassMetadata(get_class($entity));
  64. $config = $this->listener->getConfiguration($em, $meta->name);
  65. if (isset( $config['childCount'])) {
  66. // We set by default 0 on insertions for childCount field
  67. $meta->getReflectionProperty($config['childCount'])->setValue($entity, 0);
  68. }
  69. }
  70. /**
  71. * {@inheritdoc}
  72. */
  73. public function processPostPersist($em, $entity)
  74. {
  75. if (count($this->pendingChildNodeInserts)) {
  76. while ($e = array_shift($this->pendingChildNodeInserts))
  77. {
  78. $this->insertNode($em, $e);
  79. }
  80. // If "childCount" property is in the schema, we recalculate child count of all entities
  81. $meta = $em->getClassMetadata(get_class($entity));
  82. $config = $this->listener->getConfiguration($em, $meta->name);
  83. if (isset($config['childCount'])) {
  84. $this->recalculateChildCountForEntities($em, get_class( $entity ));
  85. }
  86. }
  87. }
  88. public function insertNode(EntityManager $em, $entity, $addNodeChildrenToAncestors = false)
  89. {
  90. $meta = $em->getClassMetadata(get_class($entity));
  91. $config = $this->listener->getConfiguration($em, $meta->name);
  92. $identifier = $meta->getSingleIdentifierFieldName();
  93. $id = $this->extractIdentifier( $em, $entity );
  94. $closureMeta = $em->getClassMetadata($config['closure']);
  95. $entityTable = $meta->getTableName();
  96. $closureTable = $closureMeta->getTableName();
  97. $entries = array();
  98. $childrenIDs = array();
  99. $ancestorsIDs = array();
  100. // If node has children it means it already has a self referencing row, so we skip its insertion
  101. if ($addNodeChildrenToAncestors === false) {
  102. $entries[] = array(
  103. 'ancestor' => $id,
  104. 'descendant' => $id,
  105. 'depth' => 0
  106. );
  107. }
  108. $parent = $meta->getReflectionProperty($config['parent'])->getValue($entity);
  109. if ( $parent ) {
  110. $parentId = $meta->getReflectionProperty($identifier)->getValue($parent);
  111. $dql = "SELECT c.ancestor, c.depth FROM {$closureMeta->name} c";
  112. $dql .= " WHERE c.descendant = {$parentId}";
  113. $ancestors = $em->createQuery($dql)->getArrayResult();
  114. foreach ($ancestors as $ancestor) {
  115. $entries[] = array(
  116. 'ancestor' => $ancestor['ancestor'],
  117. 'descendant' => $id,
  118. 'depth' => $ancestor['depth'] + 1
  119. );
  120. $ancestorsIDs[] = $ancestor['ancestor'];
  121. if ($addNodeChildrenToAncestors === true) {
  122. $dql = "SELECT c.descendant, c.depth FROM {$closureMeta->name} c";
  123. $dql .= " WHERE c.ancestor = {$id} AND c.ancestor != c.descendant";
  124. $children = $em->createQuery($dql)
  125. ->getArrayResult();
  126. foreach ($children as $child)
  127. {
  128. $entries[] = array(
  129. 'ancestor' => $ancestor['ancestor'],
  130. 'descendant' => $child['descendant'],
  131. 'depth' => $child['depth'] + 1
  132. );
  133. $childrenIDs[] = $child['descendant'];
  134. }
  135. }
  136. }
  137. }
  138. foreach ($entries as $closure) {
  139. if (!$em->getConnection()->insert($closureTable, $closure)) {
  140. throw new \Gedmo\Exception\RuntimeException('Failed to insert new Closure record');
  141. }
  142. }
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function processScheduledUpdate($em, $entity)
  148. {
  149. $entityClass = get_class($entity);
  150. $config = $this->listener->getConfiguration($em, $entityClass);
  151. $meta = $em->getClassMetadata($entityClass);
  152. $uow = $em->getUnitOfWork();
  153. $changeSet = $uow->getEntityChangeSet($entity);
  154. if (array_key_exists($config['parent'], $changeSet)) {
  155. $this->updateNode($em, $entity, $changeSet[$config['parent']]);
  156. }
  157. // If "childCount" property is in the schema, we recalculate child count of all entities
  158. if (isset($config['childCount'])) {
  159. $this->recalculateChildCountForEntities($em, get_class( $entity ));
  160. }
  161. }
  162. public function updateNode(EntityManager $em, $entity, array $change)
  163. {
  164. $meta = $em->getClassMetadata(get_class($entity));
  165. $config = $this->listener->getConfiguration($em, $meta->name);
  166. $closureMeta = $em->getClassMetadata($config['closure']);
  167. $oldParent = $change[0];
  168. $nodeId = $this->extractIdentifier($em, $entity);
  169. $table = $closureMeta->getTableName();
  170. if ($oldParent) {
  171. $this->removeClosurePathsOfNodeID($em, $table, $nodeId);
  172. $this->insertNode($em, $entity, true);
  173. }
  174. //\Doctrine\Common\Util\Debug::dump($oldParent);
  175. //die();
  176. }
  177. /**
  178. * {@inheritdoc}
  179. */
  180. public function processScheduledDelete($em, $entity)
  181. {
  182. $this->removeNode($em, $entity);
  183. // If "childCount" property is in the schema, we recalculate child count of all entities
  184. $meta = $em->getClassMetadata(get_class($entity));
  185. $config = $this->listener->getConfiguration($em, $meta->name);
  186. if (isset($config['childCount'])) {
  187. $this->recalculateChildCountForEntities($em, get_class( $entity ));
  188. }
  189. }
  190. public function removeNode(EntityManager $em, $entity, $maintainSelfReferencingRow = false, $maintainSelfReferencingRowOfChildren = false)
  191. {
  192. $meta = $em->getClassMetadata(get_class($entity));
  193. $config = $this->listener->getConfiguration($em, $meta->name);
  194. $closureMeta = $em->getClassMetadata($config['closure']);
  195. $id = $this->extractIdentifier( $em, $entity );
  196. $this->removeClosurePathsOfNodeID($em, $closureMeta->getTableName(), $id, $maintainSelfReferencingRow, $maintainSelfReferencingRowOfChildren);
  197. }
  198. public function removeClosurePathsOfNodeID(EntityManager $em, $table, $nodeId, $maintainSelfReferencingRow = true, $maintainSelfReferencingRowOfChildren = true)
  199. {
  200. $subquery = "SELECT c1.id FROM {$table} c1 ";
  201. $subquery .= "WHERE c1.descendant IN ( SELECT c2.descendant FROM {$table} c2 WHERE c2.ancestor = :id ) ";
  202. $subquery .= "AND ( c1.ancestor IN ( SELECT c3.ancestor FROM {$table} c3 WHERE c3.descendant = :id ";
  203. if ($maintainSelfReferencingRow === true)
  204. {
  205. $subquery .= "AND c3.descendant != c3.ancestor ";
  206. }
  207. if ( $maintainSelfReferencingRowOfChildren === false )
  208. {
  209. $subquery .= " OR c1.descendant = c1.ancestor ";
  210. }
  211. $subquery .= " ) ) ";
  212. $subquery = "DELETE FROM {$table} WHERE {$table}.id IN ( SELECT temp_table.id FROM ( {$subquery} ) temp_table )";
  213. if (!$em->getConnection()->executeQuery($subquery, array('id' => $nodeId))) {
  214. throw new \Gedmo\Exception\RuntimeException('Failed to delete old Closure records');
  215. }
  216. }
  217. public function recalculateChildCountForEntities($em, $entityClass)
  218. {
  219. $meta = $em->getClassMetadata($entityClass);
  220. $config = $this->listener->getConfiguration($em, $meta->name);
  221. $entityIdentifierField = $meta->getIdentifierColumnNames();
  222. $entityIdentifierField = $entityIdentifierField[ 0 ];
  223. $childCountField = $config['childCount'];
  224. $closureMeta = $em->getClassMetadata($config['closure']);
  225. $entityTable = $meta->getTableName();
  226. $closureTable = $closureMeta->getTableName();
  227. $subquery = "( SELECT COUNT( c2.descendant ) FROM {$closureTable} c2 WHERE c2.ancestor = c1.{$entityIdentifierField} AND c2.ancestor != c2.descendant )";
  228. $sql = "UPDATE {$entityTable} c1 SET c1.{$childCountField} = {$subquery}";
  229. if (!$em->getConnection()->executeQuery($sql)) {
  230. throw new \Gedmo\Exception\RuntimeException('Failed to update child count field of entities');
  231. }
  232. }
  233. private function extractIdentifier($em, $entity, $single = true)
  234. {
  235. if ($entity instanceof Proxy) {
  236. $id = $em->getUnitOfWork()->getEntityIdentifier($entity);
  237. } else {
  238. $meta = $em->getClassMetadata(get_class($entity));
  239. $id = array();
  240. foreach ($meta->identifier as $name) {
  241. $id[$name] = $meta->getReflectionProperty($name)->getValue($entity);
  242. }
  243. }
  244. if ($single) {
  245. $id = current($id);
  246. }
  247. return $id;
  248. }
  249. /**
  250. * {@inheritdoc}
  251. */
  252. public function onFlushEnd($em)
  253. {
  254. }
  255. }