Переглянути джерело

[Tree] Closure: Now the level property is not mandatory to build a tree

comfortablynumb 13 роки тому
батько
коміт
cea698c936

+ 0 - 18
lib/Gedmo/Exception/TreeLevelFieldNotFoundException.php

@@ -1,18 +0,0 @@
-<?php
-
-namespace Gedmo\Exception;
-
-use Gedmo\Exception;
-
-/**
- * TreeLevelFieldNotFoundException
- * 
- * @author Gustavo Falco <comfortablynumb84@gmail.com>
- * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
- * @package Gedmo.Exception
- * @subpackage TreeLevelFieldNotFoundException
- * @link http://www.gediminasm.org
- * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
- */
-class TreeLevelFieldNotFoundException extends RuntimeException
-{}

+ 11 - 27
lib/Gedmo/Tree/Entity/Repository/ClosureTreeRepository.php

@@ -8,7 +8,6 @@ use Gedmo\Tree\Strategy;
 use Gedmo\Tree\Strategy\ORM\Closure;
 use Gedmo\Tool\Wrapper\EntityWrapper;
 use Doctrine\ORM\Proxy\Proxy;
-use Gedmo\Exception\TreeLevelFieldNotFoundException;
 
 /**
  * The ClosureTreeRepository has some useful functions
@@ -24,6 +23,9 @@ use Gedmo\Exception\TreeLevelFieldNotFoundException;
  */
 class ClosureTreeRepository extends AbstractTreeRepository
 {
+    /** Alias for the level value used in the subquery of the getNodesHierarchy method */
+    const SUBQUERY_LEVEL = 'level';
+
     /**
      * Get all root nodes query
      *
@@ -293,18 +295,8 @@ class ClosureTreeRepository extends AbstractTreeRepository
     {
         $meta = $this->getClassMetadata();
         $config = $this->listener->getConfiguration($this->_em, $meta->name);
-
-        if (!isset($config['level'])) {
-            $msg = sprintf('TreeLevel field is needed on class "%s" to be allowed to call "%s" method.',
-                $meta->name,
-                'buildTreeArray'
-            );
-
-            throw new TreeLevelFieldNotFoundException($msg);
-        }
-
         $nestedTree = array();
-        $levelField = $config['level'];
+        $levelField = self::SUBQUERY_LEVEL;
         $idField = $meta->getSingleIdentifierFieldName();
 
         if (count($nodes) > 0) {
@@ -314,9 +306,10 @@ class ClosureTreeRepository extends AbstractTreeRepository
             foreach ($nodes as $n) {
                 $node = $n[0]['descendant'];
                 $node['__children'] = array();
+                $level = $n[$levelField];
 
-                if ($l < $node[$levelField]) {
-                    $l = $node[$levelField];
+                if ($l < $level) {
+                    $l = $level;
                 }
 
                 if ($l == 1) {
@@ -342,25 +335,16 @@ class ClosureTreeRepository extends AbstractTreeRepository
     public function getNodesHierarchy($node, $direct, array $config, array $options = array())
     {
         $meta = $this->getClassMetadata();
-
-        if (!isset($config['level'])) {
-            $msg = sprintf('TreeLevel field is needed on class "%s" to be allowed to call "%s" method.',
-                $meta->name,
-                'buildTreeArray'
-            );
-
-            throw new TreeLevelFieldNotFoundException($msg);
-        }
-
         $idField = $meta->getSingleIdentifierFieldName();
-
+        $subQuery = '(SELECT MAX(c2.depth) + 1 FROM '.$config['closure'];
+        $subQuery .= ' c2 WHERE c2.descendant = c.descendant GROUP BY c2.descendant) AS '.self::SUBQUERY_LEVEL;
         $q = $this->_em->createQueryBuilder()
-            ->select('c, node, p.'.$idField.' AS parent_id')
+            ->select('c, node, p.'.$idField.' AS parent_id, '.$subQuery)
             ->from($config['closure'], 'c')
             ->innerJoin('c.descendant', 'node')
             ->leftJoin('node.parent', 'p')
             ->where('c.ancestor = :node')
-            ->addOrderBy('node.'.$config['level'], 'asc');
+            ->addOrderBy('level', 'asc');
 
         $defaultOptions = array();
         $options = array_merge($defaultOptions, $options);

+ 38 - 34
tests/Gedmo/Tree/ClosureTreeRepositoryTest.php

@@ -32,11 +32,12 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $evm->addEventSubscriber(new TreeListener);
 
         $this->getMockSqliteEntityManager($evm);
-        $this->populate();
     }
 
     public function testChildCount()
     {
+        $this->populate();
+
         $repo = $this->em->getRepository(self::CATEGORY);
         $food = $repo->findOneByTitle('Food');
 
@@ -53,6 +54,8 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
 
     public function testPath()
     {
+        $this->populate();
+
         $repo = $this->em->getRepository(self::CATEGORY);
         $fruits = $repo->findOneByTitle('Fruits');
 
@@ -72,6 +75,8 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
 
     public function testChildren()
     {
+        $this->populate();
+
         $repo = $this->em->getRepository(self::CATEGORY);
         $fruits = $repo->findOneByTitle('Fruits');
 
@@ -102,6 +107,8 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
 
     public function testSingleNodeRemoval()
     {
+        $this->populate();
+
         $repo = $this->em->getRepository(self::CATEGORY);
         $fruits = $repo->findOneByTitle('Fruits');
 
@@ -129,9 +136,23 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertCount(4, $repo->children(null, true));
     }
 
-    public function testBuildTree()
+    public function testBuildTreeWithLevelProperty()
     {
-        $repo = $this->em->getRepository(self::CATEGORY);
+        $this->populate();
+
+        $this->buildTreeTests(self::CATEGORY);
+    }
+
+    public function testBuildTreeWithoutLevelProperty()
+    {
+        $this->populate(self::CATEGORY_WITHOUT_LEVEL);
+
+        $this->buildTreeTests(self::CATEGORY_WITHOUT_LEVEL);
+    }
+
+    protected function buildTreeTests($class)
+    {
+        $repo = $this->em->getRepository($class);
         $roots = $repo->getRootNodes();
         $tree = $repo->childrenHierarchy(
             $roots[0],
@@ -157,7 +178,7 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $food = $repo->findOneByTitle('Food');
         $vegitables = $repo->findOneByTitle('Vegitables');
 
-        $boringFood = new Category();
+        $boringFood = new $class();
         $boringFood->setTitle('Boring Food');
         $boringFood->setParent($food);
         $vegitables->setParent($boringFood);
@@ -190,23 +211,6 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         $this->assertEquals('Carrots', $vegitables['__children'][1]['title']);
     }
 
-    /**
-     * @expectedException Gedmo\Exception\TreeLevelFieldNotFoundException
-     */
-    public function test_buildTreeArray_IfLevelFieldIsNotPresentThrowException()
-    {
-        $repo = $this->em->getRepository(self::CATEGORY_WITHOUT_LEVEL);
-        $repo->buildTreeArray(array());
-    }
-
-    /**
-     * @expectedException Gedmo\Exception\TreeLevelFieldNotFoundException
-     */
-    public function test_getNodesHierarchy_IfLevelFieldIsNotPresentThrowException()
-    {
-        $repo = $this->em->getRepository(self::CATEGORY_WITHOUT_LEVEL);
-        $repo->getNodesHierarchy(new CategoryWithoutLevel(), true, array());
-    }
 
     protected function getUsedEntityFixtures()
     {
@@ -218,63 +222,63 @@ class ClosureTreeRepositoryTest extends BaseTestCaseORM
         );
     }
 
-    private function populate()
+    private function populate($class = self::CATEGORY)
     {
-        $food = new Category;
+        $food = new $class;
         $food->setTitle("Food");
         $this->em->persist($food);
 
-        $vegitables = new Category;
+        $vegitables = new $class;
         $vegitables->setTitle('Vegitables');
         $vegitables->setParent($food);
         $this->em->persist($vegitables);
 
-        $fruits = new Category;
+        $fruits = new $class;
         $fruits->setTitle('Fruits');
         $fruits->setParent($food);
         $this->em->persist($fruits);
 
-        $oranges = new Category;
+        $oranges = new $class;
         $oranges->setTitle('Oranges');
         $oranges->setParent($fruits);
         $this->em->persist($oranges);
 
-        $lemons = new Category;
+        $lemons = new $class;
         $lemons->setTitle('Lemons');
         $lemons->setParent($fruits);
         $this->em->persist($lemons);
 
-        $berries = new Category;
+        $berries = new $class;
         $berries->setTitle('Berries');
         $berries->setParent($fruits);
         $this->em->persist($berries);
 
-        $strawberries = new Category;
+        $strawberries = new $class;
         $strawberries->setTitle('Strawberries');
         $strawberries->setParent($berries);
         $this->em->persist($strawberries);
 
-        $cabbages = new Category;
+        $cabbages = new $class;
         $cabbages->setTitle('Cabbages');
         $cabbages->setParent($vegitables);
         $this->em->persist($cabbages);
 
-        $carrots = new Category;
+        $carrots = new $class;
         $carrots->setTitle('Carrots');
         $carrots->setParent($vegitables);
         $this->em->persist($carrots);
 
-        $milk = new Category;
+        $milk = new $class;
         $milk->setTitle('Milk');
         $milk->setParent($food);
         $this->em->persist($milk);
 
-        $cheese = new Category;
+        $cheese = new $class;
         $cheese->setTitle('Cheese');
         $cheese->setParent($milk);
         $this->em->persist($cheese);
 
-        $mouldCheese = new Category;
+        $mouldCheese = new $class;
         $mouldCheese->setTitle('Mould cheese');
         $mouldCheese->setParent($cheese);
         $this->em->persist($mouldCheese);

+ 3 - 3
tests/Gedmo/Tree/Fixture/Closure/CategoryWithoutLevel.php

@@ -27,7 +27,7 @@ class CategoryWithoutLevel
     /**
      * @Gedmo\TreeParent
      * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
-     * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
+     * @ORM\ManyToOne(targetEntity="CategoryWithoutLevel", inversedBy="children")
      */
     private $parent;
 
@@ -46,7 +46,7 @@ class CategoryWithoutLevel
         return $this->title;
     }
 
-    public function setParent(Category $parent = null)
+    public function setParent(CategoryWithoutLevel $parent = null)
     {
         $this->parent = $parent;
     }
@@ -56,7 +56,7 @@ class CategoryWithoutLevel
         return $this->parent;
     }
 
-    public function addClosure(CategoryClosure $closure)
+    public function addClosure(CategoryWithoutLevelClosure $closure)
     {
         $this->closures[] = $closure;
     }