|
36 | 36 | */ |
37 | 37 | class NestedTreeRepository extends AbstractTreeRepository |
38 | 38 | { |
| 39 | + public const TRAVERSAL_PRE_ORDER = 'pre_order'; |
| 40 | + public const TRAVERSAL_LEVEL_ORDER = 'level_order'; |
| 41 | + |
39 | 42 | /** |
40 | 43 | * Allows the following 'virtual' methods: |
41 | 44 | * - persistAsFirstChild($node) |
@@ -905,6 +908,100 @@ public function getNodesHierarchy($node = null, $direct = false, array $options |
905 | 908 | return $this->getNodesHierarchyQuery($node, $direct, $options, $includeNode)->getArrayResult(); |
906 | 909 | } |
907 | 910 |
|
| 911 | + /** |
| 912 | + * @param object $root Root node of the parsed tree |
| 913 | + * @param object|null $node Current node. If null, first node will be returned |
| 914 | + * @param int|null $limit Maximum nodes to return. If null, all nodes will be returned |
| 915 | + * @param self::TRAVERSAL_* $traversalStrategy Strategy to use to traverse tree |
| 916 | + * |
| 917 | + * @throws InvalidArgumentException if input is invalid |
| 918 | + * |
| 919 | + * @return QueryBuilder QueryBuilder object |
| 920 | + */ |
| 921 | + public function getNextNodesQueryBuilder($root, $node = null, int $limit = null, string $traversalStrategy = self::TRAVERSAL_PRE_ORDER) |
| 922 | + { |
| 923 | + $meta = $this->getClassMetadata(); |
| 924 | + $config = $this->listener->getConfiguration($this->_em, $meta->getName()); |
| 925 | + |
| 926 | + if (self::TRAVERSAL_PRE_ORDER === $traversalStrategy) { |
| 927 | + $qb = $this->childrenQueryBuilder($root, false, $config['left'], 'ASC', true); |
| 928 | + if (null !== $node) { |
| 929 | + $wrapped = new EntityWrapper($node, $this->_em); |
| 930 | + $qb |
| 931 | + ->andWhere($qb->expr()->gt('node.'.$config['left'], ':lft')) |
| 932 | + ->setParameter('lft', $wrapped->getPropertyValue($config['left'])) |
| 933 | + ; |
| 934 | + } |
| 935 | + } elseif (self::TRAVERSAL_LEVEL_ORDER === $traversalStrategy) { |
| 936 | + if (!isset($config['level'])) { |
| 937 | + throw new \InvalidArgumentException('TreeLevel must be set to use level order traversal.'); |
| 938 | + } |
| 939 | + $qb = $this->childrenQueryBuilder($root, false, [$config['level'], $config['left']], ['DESC', 'ASC'], true); |
| 940 | + if (null !== $node) { |
| 941 | + $wrapped = new EntityWrapper($node, $this->_em); |
| 942 | + $qb |
| 943 | + ->andWhere( |
| 944 | + $qb->expr()->orX( |
| 945 | + $qb->expr()->andX( |
| 946 | + $qb->expr()->gt('node.'.$config['left'], ':lft'), |
| 947 | + $qb->expr()->eq('node.'.$config['level'], ':lvl') |
| 948 | + ), |
| 949 | + $qb->expr()->lt('node.'.$config['level'], ':lvl') |
| 950 | + ) |
| 951 | + ) |
| 952 | + ->setParameter('lvl', $wrapped->getPropertyValue($config['level'])) |
| 953 | + ->setParameter('lft', $wrapped->getPropertyValue($config['left'])) |
| 954 | + ; |
| 955 | + } |
| 956 | + } else { |
| 957 | + throw new InvalidArgumentException('Invalid traversal strategy.'); |
| 958 | + } |
| 959 | + |
| 960 | + if (null !== $limit) { |
| 961 | + $qb->setMaxResults($limit); |
| 962 | + } |
| 963 | + |
| 964 | + return $qb; |
| 965 | + } |
| 966 | + |
| 967 | + /** |
| 968 | + * @param object $root Root node of the parsed tree |
| 969 | + * @param object|null $node Current node. If null, first node will be returned |
| 970 | + * @param int|null $limit Maximum nodes to return. If null, all nodes will be returned |
| 971 | + * @param self::TRAVERSAL_* $traversalStrategy Strategy to use to traverse tree |
| 972 | + * |
| 973 | + * @return Query |
| 974 | + */ |
| 975 | + public function getNextNodesQuery($root, $node = null, int $limit = null, string $traversalStrategy = self::TRAVERSAL_PRE_ORDER) |
| 976 | + { |
| 977 | + return $this->getNextNodesQueryBuilder($root, $node, $limit, $traversalStrategy)->getQuery(); |
| 978 | + } |
| 979 | + |
| 980 | + /** |
| 981 | + * @param object $root Root node of the parsed tree |
| 982 | + * @param object|null $node Current node. If null, first node will be returned |
| 983 | + * @param self::TRAVERSAL_* $traversalStrategy Strategy to use to traverse tree |
| 984 | + * |
| 985 | + * @return object|null |
| 986 | + */ |
| 987 | + public function getNextNode($root, $node = null, string $traversalStrategy = self::TRAVERSAL_PRE_ORDER) |
| 988 | + { |
| 989 | + return $this->getNextNodesQuery($root, $node, 1, $traversalStrategy)->getOneOrNullResult(); |
| 990 | + } |
| 991 | + |
| 992 | + /** |
| 993 | + * @param object $root Root node of the parsed tree |
| 994 | + * @param object|null $node Current node. If null, first node will be returned |
| 995 | + * @param int|null $limit Maximum nodes to return. If null, all nodes will be returned |
| 996 | + * @param self::TRAVERSAL_* $traversalStrategy Strategy to use to traverse tree |
| 997 | + * |
| 998 | + * @return array<object> |
| 999 | + */ |
| 1000 | + public function getNextNodes($root, $node = null, int $limit = null, string $traversalStrategy = self::TRAVERSAL_PRE_ORDER) |
| 1001 | + { |
| 1002 | + return $this->getNextNodesQuery($root, $node, $limit, $traversalStrategy)->getArrayResult(); |
| 1003 | + } |
| 1004 | + |
908 | 1005 | protected function validate() |
909 | 1006 | { |
910 | 1007 | return Strategy::NESTED === $this->listener->getStrategy($this->_em, $this->getClassMetadata()->name)->getName(); |
|
0 commit comments