Browse Source

[Tree] Added "buildTree" support for Materialized Path strategy (ODM). Done some general refactor on repositories too.

comfortablynumb 13 năm trước cách đây
mục cha
commit
f1e372f195

+ 70 - 1
lib/Gedmo/Tree/Document/MongoDB/Repository/AbstractTreeRepository.php

@@ -5,7 +5,9 @@ namespace Gedmo\Tree\Document\MongoDB\Repository;
 use Doctrine\ODM\MongoDB\DocumentRepository,
     Doctrine\ODM\MongoDB\DocumentManager,
     Doctrine\ODM\MongoDB\Mapping\ClassMetadata,
-    Doctrine\ODM\MongoDB\UnitOfWork;
+    Doctrine\ODM\MongoDB\UnitOfWork,
+    Gedmo\Tree\RepositoryUtils,
+    Gedmo\Tree\RepositoryUtilsInterface;
 
 abstract class AbstractTreeRepository extends DocumentRepository
 {
@@ -16,6 +18,11 @@ abstract class AbstractTreeRepository extends DocumentRepository
      */
     protected $listener = null;
 
+    /**
+     * Repository utils
+     */
+    protected $repoUtils = null;
+
     /**
      * {@inheritdoc}
      */
@@ -43,6 +50,56 @@ abstract class AbstractTreeRepository extends DocumentRepository
         if (!$this->validate()) {
             throw new \Gedmo\Exception\InvalidMappingException('This repository cannot be used for tree type: ' . $treeListener->getStrategy($em, $class->name)->getName());
         }
+
+        $this->repoUtils = new RepositoryUtils($this->dm, $this->getClassMetadata(), $this->listener, $this);
+    }
+
+    /**
+     * Sets the RepositoryUtilsInterface instance
+     *
+     * @param \Gedmo\Tree\RepositoryUtilsInterface $repoUtils
+     *
+     * @return $this
+     */
+    public function setRepoUtils(RepositoryUtilsInterface $repoUtils)
+    {
+        $this->repoUtils = $repoUtils;
+
+        return $this;
+    }
+
+    /**
+     * Returns the RepositoryUtilsInterface instance
+     *
+     * @return \Gedmo\Tree\RepositoryUtilsInterface|null
+     */
+    public function getRepoUtils()
+    {
+        return $this->repoUtils;
+    }
+
+    /**
+     * @see \Gedmo\Tree\RepositoryUtilsInterface::childrenHierarchy
+     */
+    public function childrenHierarchy($node = null, $direct = false, array $options = array())
+    {
+        return $this->repoUtils->childrenHierarchy($node, $direct, $options);
+    }
+
+    /**
+     * @see \Gedmo\Tree\RepositoryUtilsInterface::buildTree
+     */
+    public function buildTree(array $nodes, array $options = array())
+    {
+        return $this->repoUtils->buildTree($nodes, $options);
+    }
+
+    /**
+     * @see \Gedmo\Tree\RepositoryUtilsInterface::buildTreeArray
+     */
+    public function buildTreeArray(array $nodes)
+    {
+        return $this->repoUtils->buildTreeArray($nodes);
     }
 
     /**
@@ -52,4 +109,16 @@ abstract class AbstractTreeRepository extends DocumentRepository
      * @return bool
      */
     abstract protected function validate();
+
+    /**
+     * Returns an array of nodes suitable for method buildTree
+     *
+     * @param object - Root node
+     * @param bool - Obtain direct children?
+     * @param array - Metadata configuration
+     * @param array - Options
+     *
+     * @return array - Array of nodes
+     */
+    abstract public function getNodesHierarchy($node, $direct, array $config, array $options = array());
 }

+ 11 - 0
lib/Gedmo/Tree/Document/MongoDB/Repository/MaterializedPathRepository.php

@@ -141,6 +141,17 @@ class MaterializedPathRepository extends AbstractTreeRepository
         return $this->getChildrenQuery($node, $direct, $sortByField, $direction)->execute();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function getNodesHierarchy($node, $direct, array $config, array $options = array())
+    {
+        $query = $this->getChildrenQuery();
+        $query->setHydrate(false);
+
+        return $query->toArray();
+    }
+
     /**
      * {@inheritdoc}
      */

+ 36 - 127
lib/Gedmo/Tree/Entity/Repository/AbstractTreeRepository.php

@@ -5,7 +5,9 @@ namespace Gedmo\Tree\Entity\Repository;
 use Doctrine\ORM\EntityRepository,
     Doctrine\ORM\EntityManager,
     Doctrine\ORM\Mapping\ClassMetadata,
-    Gedmo\Tool\Wrapper\EntityWrapper;
+    Gedmo\Tool\Wrapper\EntityWrapper,
+    Gedmo\Tree\RepositoryUtils,
+    Gedmo\Tree\RepositoryUtilsInterface;
 
 abstract class AbstractTreeRepository extends EntityRepository
 {
@@ -16,6 +18,11 @@ abstract class AbstractTreeRepository extends EntityRepository
      */
     protected $listener = null;
 
+    /**
+     * Repository utils
+     */
+    protected $repoUtils = null;
+
     /**
      * {@inheritdoc}
      */
@@ -43,154 +50,56 @@ abstract class AbstractTreeRepository extends EntityRepository
         if (!$this->validate()) {
             throw new \Gedmo\Exception\InvalidMappingException('This repository cannot be used for tree type: ' . $treeListener->getStrategy($em, $class->name)->getName());
         }
+
+        $this->repoUtils = new \Gedmo\Tree\RepositoryUtils($this->_em, $this->getClassMetadata(), $this->listener, $this);
     }
 
     /**
-     * Retrieves the nested array or the decorated output.
-     * Uses @options to handle decorations
+     * Sets the RepositoryUtilsInterface instance
      *
-     * @throws \Gedmo\Exception\InvalidArgumentException
-     * @param object $node - from which node to start reordering the tree
-     * @param boolean $direct - true to take only direct children
-     * @param array $options :
-     *     decorate: boolean (false) - retrieves tree as UL->LI tree
-     *     nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
-     *     rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
-     *     rootClose: string ('</ul>') - branch close
-     *     childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
-     *     childClose: string ('</li>') - close of node
-     *     childSort: array || keys allowed: field: field to sort on, dir: direction. 'asc' or 'desc'
+     * @param \Gedmo\Tree\RepositoryUtilsInterface $repoUtils
      *
-     * @return array|string
+     * @return $this
      */
-    public function childrenHierarchy($node = null, $direct = false, array $options = array())
+    public function setRepoUtils(RepositoryUtilsInterface $repoUtils)
     {
-        $meta = $this->getClassMetadata();
-        $config = $this->listener->getConfiguration($this->_em, $meta->name);
-
-        if ($node !== null) {
-            if ($node instanceof $meta->name) {
-                $wrapped = new EntityWrapper($node, $this->_em);
-                if (!$wrapped->hasValidIdentifier()) {
-                    throw new InvalidArgumentException("Node is not managed by UnitOfWork");
-                }
-            }
-        }
-
-        // Gets the array of $node results. It must be ordered by depth
-        $nodes = $this->getNodesHierarchy($node, $direct, $config, $options);
+        $this->repoUtils = $repoUtils;
 
-        return $this->buildTree($nodes, $options);
+        return $this;
     }
 
     /**
-     * Retrieves the nested array or the decorated output.
-     * Uses @options to handle decorations
-     * NOTE: @nodes should be fetched and hydrated as array
+     * Returns the RepositoryUtilsInterface instance
      *
-     * @throws \Gedmo\Exception\InvalidArgumentException
-     * @param array $nodes - list o nodes to build tree
-     * @param array $options :
-     *     decorate: boolean (false) - retrieves tree as UL->LI tree
-     *     nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
-     *     rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
-     *     rootClose: string ('</ul>') - branch close
-     *     childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
-     *     childClose: string ('</li>') - close of node
-     *
-     * @return array|string
+     * @return \Gedmo\Tree\RepositoryUtilsInterface|null
      */
-    public function buildTree(array $nodes, array $options = array())
+    public function getRepoUtils()
     {
-        $meta = $this->getClassMetadata();
-        $nestedTree = $this->buildTreeArray($nodes);
-
-        $default = array(
-            'decorate' => false,
-            'rootOpen' => '<ul>',
-            'rootClose' => '</ul>',
-            'childOpen' => '<li>',
-            'childClose' => '</li>',
-            'nodeDecorator' => function ($node) use ($meta) {
-                // override and change it, guessing which field to use
-                if ($meta->hasField('title')) {
-                    $field = 'title';
-                } else if ($meta->hasField('name')) {
-                    $field = 'name';
-                } else {
-                    throw new InvalidArgumentException("Cannot find any representation field");
-                }
-                return $node[$field];
-            }
-        );
-        $options = array_merge($default, $options);
-        // If you don't want any html output it will return the nested array
-        if (!$options['decorate']) {
-            return $nestedTree;
-        } elseif (!count($nestedTree)) {
-            return '';
-        }
+        return $this->repoUtils;
+    }
 
-        $build = function($tree) use (&$build, &$options) {
-            $output = is_string($options['rootOpen']) ? $options['rootOpen'] : $options['rootOpen']($tree);
-            foreach ($tree as $node) {
-                $output .= is_string($options['childOpen']) ? $options['childOpen'] : $options['childOpen']($node);
-                $output .= $options['nodeDecorator']($node);
-                if (count($node['__children']) > 0) {
-                    $output .= $build($node['__children']);
-                }
-                $output .= is_string($options['childClose']) ? $options['childClose'] : $options['childClose']($node);
-            }
-            return $output . (is_string($options['rootClose']) ? $options['rootClose'] : $options['rootClose']($tree));
-        };
+    /**
+     * @see \Gedmo\Tree\RepositoryUtilsInterface::childrenHierarchy
+     */
+    public function childrenHierarchy($node = null, $direct = false, array $options = array())
+    {
+        return $this->repoUtils->childrenHierarchy($node, $direct, $options);
+    }
 
-        return $build($nestedTree);
+    /**
+     * @see \Gedmo\Tree\RepositoryUtilsInterface::buildTree
+     */
+    public function buildTree(array $nodes, array $options = array())
+    {
+        return $this->repoUtils->buildTree($nodes, $options);
     }
 
     /**
-     * Process nodes and produce an array with the
-     * structure of the tree
-     *
-     * @param array - Array of nodes
-     *
-     * @return array - Array with tree structure
+     * @see \Gedmo\Tree\RepositoryUtilsInterface::buildTreeArray
      */
     public function buildTreeArray(array $nodes)
     {
-        $meta = $this->getClassMetadata();
-        $config = $this->listener->getConfiguration($this->_em, $meta->name);
-        $nestedTree = array();
-        $l = 0;
-
-        if (count($nodes) > 0) {
-            // Node Stack. Used to help building the hierarchy
-            $stack = array();
-            foreach ($nodes as $child) {
-                $item = $child;
-                $item['__children'] = array();
-                // Number of stack items
-                $l = count($stack);
-                // Check if we're dealing with different levels
-                while($l > 0 && $stack[$l - 1][$config['level']] >= $item[$config['level']]) {
-                    array_pop($stack);
-                    $l--;
-                }
-                // Stack is empty (we are inspecting the root)
-                if ($l == 0) {
-                    // Assigning the root child
-                    $i = count($nestedTree);
-                    $nestedTree[$i] = $item;
-                    $stack[] = &$nestedTree[$i];
-                } else {
-                    // Add child to parent
-                    $i = count($stack[$l - 1]['__children']);
-                    $stack[$l - 1]['__children'][$i] = $item;
-                    $stack[] = &$stack[$l - 1]['__children'][$i];
-                }
-            }
-        }
-
-        return $nestedTree;
+        return $this->repoUtils->buildTreeArray($nodes);
     }
 
     /**

+ 181 - 0
lib/Gedmo/Tree/RepositoryUtils.php

@@ -0,0 +1,181 @@
+<?php
+
+namespace Gedmo\Tree;
+
+use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Doctrine\Common\Persistence\ObjectManager;
+
+class RepositoryUtils implements RepositoryUtilsInterface
+{
+    protected $meta;
+
+    protected $listener;
+
+    protected $om;
+
+    protected $repo;
+
+    public function __construct(ObjectManager $om, ClassMetadata $meta, $listener, $repo)
+    {
+        $this->om = $om;
+        $this->meta = $meta;
+        $this->listener = $listener;
+        $this->repo = $repo;
+    }
+
+    public function getClassMetadata()
+    {
+        return $this->meta;
+    }
+
+    /**
+     * Retrieves the nested array or the decorated output.
+     * Uses @options to handle decorations
+     *
+     * @throws \Gedmo\Exception\InvalidArgumentException
+     * @param object $node - from which node to start reordering the tree
+     * @param boolean $direct - true to take only direct children
+     * @param array $options :
+     *     decorate: boolean (false) - retrieves tree as UL->LI tree
+     *     nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
+     *     rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
+     *     rootClose: string ('</ul>') - branch close
+     *     childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
+     *     childClose: string ('</li>') - close of node
+     *     childSort: array || keys allowed: field: field to sort on, dir: direction. 'asc' or 'desc'
+     *
+     * @return array|string
+     */
+    public function childrenHierarchy($node = null, $direct = false, array $options = array())
+    {
+        $meta = $this->getClassMetadata();
+        $config = $this->listener->getConfiguration($this->om, $meta->name);
+
+        if ($node !== null) {
+            if ($node instanceof $meta->name) {
+                $wrapperClass = $this->om instanceof \Doctrine\ORM\EntityManager ?
+                    '\Gedmo\Tool\Wrapper\EntityWrapper' :
+                    '\Gedmo\Tool\Wrapper\MongoDocumentWrapper';
+                $wrapped = new $wrapperClass($node, $this->om);
+                if (!$wrapped->hasValidIdentifier()) {
+                    throw new InvalidArgumentException("Node is not managed by UnitOfWork");
+                }
+            }
+        }
+
+        // Gets the array of $node results. It must be ordered by depth
+        $nodes = $this->repo->getNodesHierarchy($node, $direct, $config, $options);
+
+        return $this->buildTree($nodes, $options);
+    }
+
+    /**
+     * Retrieves the nested array or the decorated output.
+     * Uses @options to handle decorations
+     * NOTE: @nodes should be fetched and hydrated as array
+     *
+     * @throws \Gedmo\Exception\InvalidArgumentException
+     * @param array $nodes - list o nodes to build tree
+     * @param array $options :
+     *     decorate: boolean (false) - retrieves tree as UL->LI tree
+     *     nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
+     *     rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
+     *     rootClose: string ('</ul>') - branch close
+     *     childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
+     *     childClose: string ('</li>') - close of node
+     *
+     * @return array|string
+     */
+    public function buildTree(array $nodes, array $options = array())
+    {
+        $meta = $this->getClassMetadata();
+        $nestedTree = $this->repo->buildTreeArray($nodes);
+
+        $default = array(
+            'decorate' => false,
+            'rootOpen' => '<ul>',
+            'rootClose' => '</ul>',
+            'childOpen' => '<li>',
+            'childClose' => '</li>',
+            'nodeDecorator' => function ($node) use ($meta) {
+                // override and change it, guessing which field to use
+                if ($meta->hasField('title')) {
+                    $field = 'title';
+                } else if ($meta->hasField('name')) {
+                    $field = 'name';
+                } else {
+                    throw new InvalidArgumentException("Cannot find any representation field");
+                }
+                return $node[$field];
+            }
+        );
+        $options = array_merge($default, $options);
+        // If you don't want any html output it will return the nested array
+        if (!$options['decorate']) {
+            return $nestedTree;
+        } elseif (!count($nestedTree)) {
+            return '';
+        }
+
+        $build = function($tree) use (&$build, &$options) {
+            $output = is_string($options['rootOpen']) ? $options['rootOpen'] : $options['rootOpen']($tree);
+            foreach ($tree as $node) {
+                $output .= is_string($options['childOpen']) ? $options['childOpen'] : $options['childOpen']($node);
+                $output .= $options['nodeDecorator']($node);
+                if (count($node['__children']) > 0) {
+                    $output .= $build($node['__children']);
+                }
+                $output .= $options['childClose'];
+            }
+            return $output . $options['rootClose'];
+        };
+
+        return $build($nestedTree);
+    }
+
+    /**
+     * Process nodes and produce an array with the
+     * structure of the tree
+     *
+     * @param array - Array of nodes
+     *
+     * @return array - Array with tree structure
+     */
+    public function buildTreeArray(array $nodes)
+    {
+        $meta = $this->getClassMetadata();
+        $config = $this->listener->getConfiguration($this->om, $meta->name);
+        $nestedTree = array();
+        $l = 0;
+
+        if (count($nodes) > 0) {
+            // Node Stack. Used to help building the hierarchy
+            $stack = array();
+            foreach ($nodes as $child) {
+                $item = $child;
+                $item['__children'] = array();
+                // Number of stack items
+                $l = count($stack);
+                // Check if we're dealing with different levels
+                while($l > 0 && $stack[$l - 1][$config['level']] >= $item[$config['level']]) {
+                    array_pop($stack);
+                    $l--;
+                }
+                // Stack is empty (we are inspecting the root)
+                if ($l == 0) {
+                    // Assigning the root child
+                    $i = count($nestedTree);
+                    $nestedTree[$i] = $item;
+                    $stack[] = &$nestedTree[$i];
+                } else {
+                    // Add child to parent
+                    $i = count($stack[$l - 1]['__children']);
+                    $stack[$l - 1]['__children'][$i] = $item;
+                    $stack[] = &$stack[$l - 1]['__children'][$i];
+                }
+            }
+        }
+
+        return $nestedTree;
+    }
+}

+ 55 - 0
lib/Gedmo/Tree/RepositoryUtilsInterface.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace Gedmo\Tree;
+
+interface RepositoryUtilsInterface
+{
+    /**
+     * Retrieves the nested array or the decorated output.
+     * Uses @options to handle decorations
+     *
+     * @throws \Gedmo\Exception\InvalidArgumentException
+     * @param object $node - from which node to start reordering the tree
+     * @param boolean $direct - true to take only direct children
+     * @param array $options :
+     *     decorate: boolean (false) - retrieves tree as UL->LI tree
+     *     nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
+     *     rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
+     *     rootClose: string ('</ul>') - branch close
+     *     childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
+     *     childClose: string ('</li>') - close of node
+     *     childSort: array || keys allowed: field: field to sort on, dir: direction. 'asc' or 'desc'
+     *
+     * @return array|string
+     */
+    public function childrenHierarchy($node = null, $direct = false, array $options = array());
+
+    /**
+     * Retrieves the nested array or the decorated output.
+     * Uses @options to handle decorations
+     * NOTE: @nodes should be fetched and hydrated as array
+     *
+     * @throws \Gedmo\Exception\InvalidArgumentException
+     * @param array $nodes - list o nodes to build tree
+     * @param array $options :
+     *     decorate: boolean (false) - retrieves tree as UL->LI tree
+     *     nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
+     *     rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
+     *     rootClose: string ('</ul>') - branch close
+     *     childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
+     *     childClose: string ('</li>') - close of node
+     *
+     * @return array|string
+     */
+    public function buildTree(array $nodes, array $options = array());
+
+    /**
+     * Process nodes and produce an array with the
+     * structure of the tree
+     *
+     * @param array - Array of nodes
+     *
+     * @return array - Array with tree structure
+     */
+    public function buildTreeArray(array $nodes);
+}

+ 18 - 0
tests/Gedmo/Tree/MaterializedPathODMMongoDBRepositoryTest.php

@@ -96,6 +96,24 @@ class MaterializedPathODMMongoDBRepositoryTest extends BaseTestCaseMongoODM
         $this->assertEquals('Sports', $tree->getNext()->getTitle());
     }
 
+    /**
+     * @test
+     */
+    function childrenHierarchy()
+    {
+        $repo = $this->dm->getRepository(self::CATEGORY);
+        $roots = $repo->getRootNodes();
+        $tree = $repo->childrenHierarchy($roots->getNext());
+
+        $vegitablesChildren = $tree[0]['__children'][1]['__children'];
+        $this->assertEquals('Food', $tree[0]['title']);
+        $this->assertEquals('Fruits', $tree[0]['__children'][0]['title']);
+        $this->assertEquals('Vegitables', $tree[0]['__children'][1]['title']);
+        $this->assertEquals('Carrots', $vegitablesChildren[0]['title']);
+        $this->assertEquals('Potatoes', $vegitablesChildren[1]['title']);
+        $this->assertEquals('Sports', $tree[1]['title']);
+    }
+
     protected function getUsedEntityFixtures()
     {
         return array(