RepositoryUtils.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. namespace Gedmo\Tree;
  3. use Doctrine\Common\Persistence\Mapping\ClassMetadata;
  4. use Doctrine\Common\Persistence\ObjectManager;
  5. class RepositoryUtils implements RepositoryUtilsInterface
  6. {
  7. protected $meta;
  8. protected $listener;
  9. protected $om;
  10. protected $repo;
  11. public function __construct(ObjectManager $om, ClassMetadata $meta, $listener, $repo)
  12. {
  13. $this->om = $om;
  14. $this->meta = $meta;
  15. $this->listener = $listener;
  16. $this->repo = $repo;
  17. }
  18. public function getClassMetadata()
  19. {
  20. return $this->meta;
  21. }
  22. /**
  23. * Retrieves the nested array or the decorated output.
  24. * Uses @options to handle decorations
  25. *
  26. * @throws \Gedmo\Exception\InvalidArgumentException
  27. * @param object $node - from which node to start reordering the tree
  28. * @param boolean $direct - true to take only direct children
  29. * @param array $options :
  30. * decorate: boolean (false) - retrieves tree as UL->LI tree
  31. * nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
  32. * rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
  33. * rootClose: string ('</ul>') - branch close
  34. * childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
  35. * childClose: string ('</li>') - close of node
  36. * childSort: array || keys allowed: field: field to sort on, dir: direction. 'asc' or 'desc'
  37. *
  38. * @return array|string
  39. */
  40. public function childrenHierarchy($node = null, $direct = false, array $options = array())
  41. {
  42. $meta = $this->getClassMetadata();
  43. $config = $this->listener->getConfiguration($this->om, $meta->name);
  44. if ($node !== null) {
  45. if ($node instanceof $meta->name) {
  46. $wrapperClass = $this->om instanceof \Doctrine\ORM\EntityManager ?
  47. '\Gedmo\Tool\Wrapper\EntityWrapper' :
  48. '\Gedmo\Tool\Wrapper\MongoDocumentWrapper';
  49. $wrapped = new $wrapperClass($node, $this->om);
  50. if (!$wrapped->hasValidIdentifier()) {
  51. throw new InvalidArgumentException("Node is not managed by UnitOfWork");
  52. }
  53. }
  54. }
  55. // Gets the array of $node results. It must be ordered by depth
  56. $nodes = $this->repo->getNodesHierarchy($node, $direct, $config, $options);
  57. return $this->buildTree($nodes, $options);
  58. }
  59. /**
  60. * Retrieves the nested array or the decorated output.
  61. * Uses @options to handle decorations
  62. * NOTE: @nodes should be fetched and hydrated as array
  63. *
  64. * @throws \Gedmo\Exception\InvalidArgumentException
  65. * @param array $nodes - list o nodes to build tree
  66. * @param array $options :
  67. * decorate: boolean (false) - retrieves tree as UL->LI tree
  68. * nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
  69. * rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
  70. * rootClose: string ('</ul>') - branch close
  71. * childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
  72. * childClose: string ('</li>') - close of node
  73. *
  74. * @return array|string
  75. */
  76. public function buildTree(array $nodes, array $options = array())
  77. {
  78. $meta = $this->getClassMetadata();
  79. $nestedTree = $this->repo->buildTreeArray($nodes);
  80. $default = array(
  81. 'decorate' => false,
  82. 'rootOpen' => '<ul>',
  83. 'rootClose' => '</ul>',
  84. 'childOpen' => '<li>',
  85. 'childClose' => '</li>',
  86. 'nodeDecorator' => function ($node) use ($meta) {
  87. // override and change it, guessing which field to use
  88. if ($meta->hasField('title')) {
  89. $field = 'title';
  90. } else if ($meta->hasField('name')) {
  91. $field = 'name';
  92. } else {
  93. throw new InvalidArgumentException("Cannot find any representation field");
  94. }
  95. return $node[$field];
  96. }
  97. );
  98. $options = array_merge($default, $options);
  99. // If you don't want any html output it will return the nested array
  100. if (!$options['decorate']) {
  101. return $nestedTree;
  102. } elseif (!count($nestedTree)) {
  103. return '';
  104. }
  105. $build = function($tree) use (&$build, &$options) {
  106. $output = is_string($options['rootOpen']) ? $options['rootOpen'] : $options['rootOpen']($tree);
  107. foreach ($tree as $node) {
  108. $output .= is_string($options['childOpen']) ? $options['childOpen'] : $options['childOpen']($node);
  109. $output .= $options['nodeDecorator']($node);
  110. if (count($node['__children']) > 0) {
  111. $output .= $build($node['__children']);
  112. }
  113. $output .= $options['childClose'];
  114. }
  115. return $output . $options['rootClose'];
  116. };
  117. return $build($nestedTree);
  118. }
  119. /**
  120. * Process nodes and produce an array with the
  121. * structure of the tree
  122. *
  123. * @param array - Array of nodes
  124. *
  125. * @return array - Array with tree structure
  126. */
  127. public function buildTreeArray(array $nodes)
  128. {
  129. $meta = $this->getClassMetadata();
  130. $config = $this->listener->getConfiguration($this->om, $meta->name);
  131. $nestedTree = array();
  132. $l = 0;
  133. if (count($nodes) > 0) {
  134. // Node Stack. Used to help building the hierarchy
  135. $stack = array();
  136. foreach ($nodes as $child) {
  137. $item = $child;
  138. $item['__children'] = array();
  139. // Number of stack items
  140. $l = count($stack);
  141. // Check if we're dealing with different levels
  142. while($l > 0 && $stack[$l - 1][$config['level']] >= $item[$config['level']]) {
  143. array_pop($stack);
  144. $l--;
  145. }
  146. // Stack is empty (we are inspecting the root)
  147. if ($l == 0) {
  148. // Assigning the root child
  149. $i = count($nestedTree);
  150. $nestedTree[$i] = $item;
  151. $stack[] = &$nestedTree[$i];
  152. } else {
  153. // Add child to parent
  154. $i = count($stack[$l - 1]['__children']);
  155. $stack[$l - 1]['__children'][$i] = $item;
  156. $stack[] = &$stack[$l - 1]['__children'][$i];
  157. }
  158. }
  159. }
  160. return $nestedTree;
  161. }
  162. }