瀏覽代碼

[Tree] Closure: Now a tree from a specific root node can be built.

comfortablynumb 13 年之前
父節點
當前提交
361ffa8dff

+ 29 - 10
lib/Gedmo/Tree/Entity/Repository/ClosureTreeRepository.php

@@ -148,10 +148,12 @@ class ClosureTreeRepository extends AbstractTreeRepository
      * @param boolean $direct - true to take only direct children
      * @param string $sortByField - field name to sort by
      * @param string $direction - sort direction : "ASC" or "DESC"
+     * @param bool $includeNode - Include the root node in the result?
+     *
      * @throws InvalidArgumentException - if input is not valid
      * @return Query
      */
-    public function childrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'ASC')
+    public function childrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
     {
         $meta = $this->getClassMetadata();
         $config = $this->listener->getConfiguration($this->_em, $meta->name);
@@ -162,14 +164,23 @@ class ClosureTreeRepository extends AbstractTreeRepository
                 if (!$this->_em->getUnitOfWork()->isInIdentityMap($node)) {
                     throw new InvalidArgumentException("Node is not managed by UnitOfWork");
                 }
+
+                $where = 'c.ancestor = :node AND ';
+
                 $qb->select('c, node')
                     ->from($config['closure'], 'c')
-                    ->innerJoin('c.descendant', 'node')
-                    ->where('c.ancestor = :node');
+                    ->innerJoin('c.descendant', 'node');
+
                 if ($direct) {
-                    $qb->andWhere('c.depth = 1');
+                    $where .= 'c.depth = 1';
                 } else {
-                    $qb->andWhere('c.descendant <> :node');
+                    $where .= 'c.descendant <> :node';
+                }
+
+                $qb->where($where);
+
+                if ($includeNode) {
+                    $qb->orWhere('c.ancestor = :node AND c.descendant = :node');
                 }
             } else {
                 throw new \InvalidArgumentException("Node is not related to this repository");
@@ -181,6 +192,7 @@ class ClosureTreeRepository extends AbstractTreeRepository
                 $qb->where('node.' . $config['parent'] . ' IS NULL');
             }
         }
+
         if ($sortByField) {
             if ($meta->hasField($sortByField) && in_array(strtolower($direction), array('asc', 'desc'))) {
                 $qb->orderBy('node.' . $sortByField, $direction);
@@ -188,11 +200,17 @@ class ClosureTreeRepository extends AbstractTreeRepository
                 throw new InvalidArgumentException("Invalid sort options specified: field - {$sortByField}, direction - {$direction}");
             }
         }
-        $q = $qb->getQuery();
+
         if ($node) {
-            $q->setParameters(compact('node'));
+            $qb->setParameter('node', $node);
         }
-        return $q;
+
+        return $qb;
+    }
+
+    public function childrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
+    {
+        return $this->childrenQueryBuilder($node, $direct, $sortByField, $direction, $includeNode)->getQuery();
     }
 
     /**
@@ -202,11 +220,12 @@ class ClosureTreeRepository extends AbstractTreeRepository
      * @param boolean $direct - true to take only direct children
      * @param string $sortByField - field name to sort by
      * @param string $direction - sort direction : "ASC" or "DESC"
+     * @param bool $includeNode - Include the root node in results?
      * @return array - list of given $node children, null on failure
      */
-    public function children($node = null, $direct = false, $sortByField = null, $direction = 'ASC')
+    public function children($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
     {
-        $result = $this->childrenQuery($node, $direct, $sortByField, $direction)->getResult();
+        $result = $this->childrenQuery($node, $direct, $sortByField, $direction, $includeNode)->getResult();
         if ($node) {
             $result = array_map(function($closure) {
                 return $closure->getDescendant();

+ 56 - 6
tests/Gedmo/Tree/ClosureTreeRepositoryTest.php

@@ -53,7 +53,7 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertEquals(4, $count);
 
         $rootCount = $repo->childCount(null, true);
-        $this->assertEquals(1, $rootCount);
+        $this->assertEquals(2, $rootCount);
     }
 
     public function testPath()
@@ -84,14 +84,22 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $repo = $this->em->getRepository(self::CATEGORY);
         $fruits = $repo->findOneByTitle('Fruits');
 
-        // direct children of node, sorted by title ascending order
+        // direct children of node, sorted by title ascending order. NOT including the root node
         $children = $repo->children($fruits, true, 'title');
         $this->assertCount(3, $children);
         $this->assertEquals('Berries', $children[0]->getTitle());
         $this->assertEquals('Lemons', $children[1]->getTitle());
         $this->assertEquals('Oranges', $children[2]->getTitle());
 
-        // all children of node
+        // direct children of node, sorted by title ascending order. including the root node
+        $children = $repo->children($fruits, true, 'title', 'asc', true);
+        $this->assertCount(4, $children);
+        $this->assertEquals('Berries', $children[0]->getTitle());
+        $this->assertEquals('Fruits', $children[1]->getTitle());
+        $this->assertEquals('Lemons', $children[2]->getTitle());
+        $this->assertEquals('Oranges', $children[3]->getTitle());
+
+        // all children of node, NOT including the root
         $children = $repo->children($fruits);
         $this->assertCount(4, $children);
         $this->assertEquals('Oranges', $children[0]->getTitle());
@@ -99,14 +107,24 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertEquals('Berries', $children[2]->getTitle());
         $this->assertEquals('Strawberries', $children[3]->getTitle());
 
+        // all children of node, including the root
+        $children = $repo->children($fruits, false, 'title', 'asc', true);
+        $this->assertCount(5, $children);
+        $this->assertEquals('Berries', $children[0]->getTitle());
+        $this->assertEquals('Fruits', $children[1]->getTitle());
+        $this->assertEquals('Lemons', $children[2]->getTitle());
+        $this->assertEquals('Oranges', $children[3]->getTitle());
+        $this->assertEquals('Strawberries', $children[4]->getTitle());
+
         // direct root nodes
         $children = $repo->children(null, true, 'title');
-        $this->assertCount(1, $children);
+        $this->assertCount(2, $children);
         $this->assertEquals('Food', $children[0]->getTitle());
+        $this->assertEquals('Sports', $children[1]->getTitle());
 
         // all tree
         $children = $repo->children();
-        $this->assertCount(12, $children);
+        $this->assertCount(15, $children);
     }
 
     public function testSingleNodeRemoval()
@@ -137,7 +155,7 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertNull($vegitables->getParent());
 
         $repo->removeFromTree($lemons);
-        $this->assertCount(4, $repo->children(null, true));
+        $this->assertCount(5, $repo->children(null, true));
     }
 
     public function testBuildTreeWithLevelProperty()
@@ -207,6 +225,15 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertEquals('Cabbages', $vegitables['__children'][0]['title']);
         $this->assertEquals('Carrots', $vegitables['__children'][1]['title']);
 
+        $tree = $repo->childrenHierarchy(
+            $roots[1],
+            false,
+            array('childSort' => array('field' => 'title', 'dir' => 'asc'))
+        );
+        $this->assertEquals('Sports', $tree[0]['title']);
+        $this->assertEquals('Soccer', $tree[0]['__children'][0]['title']);
+        $this->assertEquals('Indoor Soccer', $tree[0]['__children'][0]['__children'][0]['title']);
+
         $food = $repo->findOneByTitle('Food');
         $vegitables = $repo->findOneByTitle('Vegitables');
 
@@ -241,6 +268,15 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertEquals('Vegitables', $boringFood['__children'][0]['title']);
         $this->assertEquals('Cabbages', $vegitables['__children'][0]['title']);
         $this->assertEquals('Carrots', $vegitables['__children'][1]['title']);
+
+        $tree = $repo->childrenHierarchy(
+            $roots[1],
+            false,
+            array('childSort' => array('field' => 'title', 'dir' => 'asc'))
+        );
+        $this->assertEquals('Sports', $tree[0]['title']);
+        $this->assertEquals('Soccer', $tree[0]['__children'][0]['title']);
+        $this->assertEquals('Indoor Soccer', $tree[0]['__children'][0]['__children'][0]['title']);
     }
 
     protected function getUsedEntityFixtures()
@@ -314,6 +350,20 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $mouldCheese->setParent($cheese);
         $this->em->persist($mouldCheese);
 
+        $sports = new $class;
+        $sports->setTitle('Sports');
+        $this->em->persist($sports);
+
+        $soccer = new $class;
+        $soccer->setTitle('Soccer');
+        $soccer->setParent($sports);
+        $this->em->persist($soccer);
+
+        $indoorSoccer = new $class;
+        $indoorSoccer->setTitle('Indoor Soccer');
+        $indoorSoccer->setParent($soccer);
+        $this->em->persist($indoorSoccer);
+
         $this->em->flush();
         $this->em->clear();
     }