Browse Source

[tree] single listener through event adapter

gediminasm 14 năm trước cách đây
mục cha
commit
1da7a2a001

+ 54 - 0
lib/Gedmo/Tree/Entity/Repository/AbstractTreeRepository.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace Gedmo\Tree\Entity\Repository;
+
+use Doctrine\ORM\EntityRepository,
+    Doctrine\ORM\EntityManager,
+    Doctrine\ORM\Mapping\ClassMetadata;
+
+abstract class AbstractTreeRepository extends EntityRepository
+{
+    /**
+     * Tree listener on event manager
+     *
+     * @var AbstractTreeListener
+     */
+    protected $listener = null;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct(EntityManager $em, ClassMetadata $class)
+    {
+        parent::__construct($em, $class);
+        $treeListener = null;
+        foreach ($em->getEventManager()->getListeners() as $event => $listeners) {
+            foreach ($listeners as $hash => $listener) {
+                if ($listener instanceof \Gedmo\Tree\TreeListener) {
+                    $treeListener = $listener;
+                    break;
+                }
+            }
+            if ($treeListener) {
+                break;
+            }
+        }
+
+        if (is_null($treeListener)) {
+            throw new \Gedmo\Exception\InvalidMappingException('This repository can be attached only to ORM tree listener');
+        }
+
+        $this->listener = $treeListener;
+        if (!$this->validates()) {
+            throw new \Gedmo\Exception\InvalidMappingException('This repository cannot be used for tree type: ' . $treeListener->getStrategy($em, $class->name)->getName());
+        }
+    }
+
+    /**
+     * Checks if current repository is right
+     * for currently used tree strategy
+     *
+     * @return bool
+     */
+    abstract protected function validates();
+}

+ 12 - 13
lib/Gedmo/Tree/Entity/Repository/ClosureTreeRepository.php

@@ -2,8 +2,7 @@
 
 namespace Gedmo\Tree\Entity\Repository;
 
-use Gedmo\Tree\AbstractTreeRepository,
-    Doctrine\ORM\Query,
+use Doctrine\ORM\Query,
     Gedmo\Tree\Strategy,
     Gedmo\Tree\Strategy\ORM\Closure,
     Doctrine\ORM\Proxy\Proxy;
@@ -13,7 +12,7 @@ use Gedmo\Tree\AbstractTreeRepository,
  * to interact with Closure tree. Repository uses
  * the strategy used by listener
  *
- * @author Gustavo Adrian <comfortablynumb84@gmail.com> 
+ * @author Gustavo Adrian <comfortablynumb84@gmail.com>
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Tree.Entity.Repository
  * @subpackage ClosureRepository
@@ -24,27 +23,27 @@ class ClosureTreeRepository extends AbstractTreeRepository
 {
     /**
      * Counts the children of given TreeNode
-     * 
+     *
      * @param object $node - The node from which we'll count its children
      * @param boolean $direct - true to count only direct children
      * @return integer
-     */ 
+     */
     public function childCount($node, $direct = false)
     {
         $meta = $this->getClassMetadata();
-        $id	= $this->getIdFromEntity($node);
+        $id    = $this->getIdFromEntity($node);
         $qb = $this->getQueryBuilder();
         $qb->select('COUNT( c.id )')
             ->from($meta->rootEntityName, 'c')
             ->where('c.ancestor = :node_id')
             ->andWhere('c.ancestor != c.descendant');
-        
+
         if ($direct === true) {
             $qb->andWhere('c.depth = 1');
         }
-        
+
         $qb->setParameter('node_id', $id);
-        
+
         return $qb->getQuery()->getSingleScalarResult();
     }
 
@@ -52,20 +51,20 @@ class ClosureTreeRepository extends AbstractTreeRepository
     protected function getQueryBuilder()
     {
         $qb = $this->_em->createQueryBuilder();
-        
+
         return $qb;
     }
-    
+
     protected function getIdFromEntity( $node )
     {
         $meta = $this->_em->getClassMetadata(get_class($node));
         $nodeID = $meta->getSingleIdentifierFieldName();
         $refProp = $meta->getReflectionProperty($nodeID);
         $id = $refProp->getValue($node);
-        
+
         return $id;
     }
-    
+
     /**
      * {@inheritdoc}
      */

+ 55 - 56
lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php

@@ -2,8 +2,7 @@
 
 namespace Gedmo\Tree\Entity\Repository;
 
-use Gedmo\Tree\AbstractTreeRepository,
-    Doctrine\ORM\Query,
+use Doctrine\ORM\Query,
     Gedmo\Tree\Strategy,
     Gedmo\Tree\Strategy\ORM\Nested,
     Gedmo\Exception\InvalidArgumentException,
@@ -13,7 +12,7 @@ use Gedmo\Tree\AbstractTreeRepository,
  * The NestedTreeRepository has some useful functions
  * to interact with NestedSet tree. Repository uses
  * the strategy used by listener
- * 
+ *
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Tree.Entity.Repository
  * @subpackage NestedTreeRepository
@@ -24,7 +23,7 @@ class NestedTreeRepository extends AbstractTreeRepository
 {
     /**
      * Get all root nodes
-     * 
+     *
      * @return array
      */
     public function getRootNodes()
@@ -38,10 +37,10 @@ class NestedTreeRepository extends AbstractTreeRepository
             ->orderBy('node.' . $config['left'], 'ASC');
         return $qb->getQuery()->getResult(Query::HYDRATE_OBJECT);
     }
-    
+
     /**
      * Get the Tree path of Nodes by given $node
-     * 
+     *
      * @param object $node
      * @return array - list of Nodes in path
      */
@@ -76,14 +75,14 @@ class NestedTreeRepository extends AbstractTreeRepository
         }
         return $result;
     }
-    
+
     /**
      * Counts the children of given TreeNode
-     * 
+     *
      * @param object $node - if null counts all records in tree
      * @param boolean $direct - true to count only direct children
      * @return integer
-     */ 
+     */
     public function childCount($node = null, $direct = false)
     {
         $count = 0;
@@ -98,7 +97,7 @@ class NestedTreeRepository extends AbstractTreeRepository
                     $qb->select('COUNT(node.' . $nodeId . ')')
                         ->from($meta->rootEntityName, 'node')
                         ->where('node.' . $config['parent'] . ' = ' . $id);
-                        
+
                     $q = $qb->getQuery();
                     $count = intval($q->getSingleScalarResult());
                 } else {
@@ -121,10 +120,10 @@ class NestedTreeRepository extends AbstractTreeRepository
         }
         return $count;
     }
-    
+
     /**
      * Get list of children followed by given $node
-     * 
+     *
      * @param object $node - if null, all tree nodes will be taken
      * @param boolean $direct - true to take only direct children
      * @param string $sortByField - field name to sort by
@@ -136,7 +135,7 @@ class NestedTreeRepository extends AbstractTreeRepository
     {
         $meta = $this->getClassMetadata();
         $config = $this->listener->getConfiguration($this->_em, $meta->name);
-             
+
         $qb = $this->_em->createQueryBuilder();
         $qb->select('node')
             ->from($meta->rootEntityName, 'node');
@@ -174,7 +173,7 @@ class NestedTreeRepository extends AbstractTreeRepository
         $q = $qb->getQuery();
         return $q->getResult(Query::HYDRATE_OBJECT);
     }
-    
+
     /**
      * Get list of leaf nodes of the tree
      *
@@ -192,7 +191,7 @@ class NestedTreeRepository extends AbstractTreeRepository
         if (isset($config['root']) && is_null($root)) {
             throw new InvalidArgumentException("If tree has root, getLiefs method requires root node");
         }
-        
+
         $qb = $this->_em->createQueryBuilder();
         $qb->select('node')
             ->from($meta->rootEntityName, 'node')
@@ -217,10 +216,10 @@ class NestedTreeRepository extends AbstractTreeRepository
         $q = $qb->getQuery();
         return $q->getResult(Query::HYDRATE_OBJECT);
     }
-    
+
     /**
      * Find the next siblings of the given $node
-     * 
+     *
      * @param object $node
      * @param bool $includeSelf - include the node itself
      * @throws \Gedmo\Exception\InvalidArgumentException - if input is invalid
@@ -242,10 +241,10 @@ class NestedTreeRepository extends AbstractTreeRepository
                 $this->_em->refresh($parent);
             }
             $parentId = $meta->getReflectionProperty($identifierField)->getValue($parent);
-            
+
             $left = $meta->getReflectionProperty($config['left'])->getValue($node);
             $sign = $includeSelf ? '>=' : '>';
-            
+
             $dql = "SELECT node FROM {$meta->rootEntityName} node";
             $dql .= " WHERE node.{$config['parent']} = {$parentId}";
             $dql .= " AND node.{$config['left']} {$sign} {$left}";
@@ -256,10 +255,10 @@ class NestedTreeRepository extends AbstractTreeRepository
         }
         return $result;
     }
-    
+
     /**
      * Find the previous siblings of the given $node
-     * 
+     *
      * @param object $node
      * @param bool $includeSelf - include the node itself
      * @throws \Gedmo\Exception\InvalidArgumentException - if input is invalid
@@ -269,7 +268,7 @@ class NestedTreeRepository extends AbstractTreeRepository
     {
         $result = array();
         $meta = $this->getClassMetadata();
-        
+
         if ($node instanceof $meta->rootEntityName) {
             $config = $this->listener->getConfiguration($this->_em, $meta->name);
             $parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
@@ -284,7 +283,7 @@ class NestedTreeRepository extends AbstractTreeRepository
 
             $left = $meta->getReflectionProperty($config['left'])->getValue($node);
             $sign = $includeSelf ? '<=' : '<';
-            
+
             $dql = "SELECT node FROM {$meta->rootEntityName} node";
             $dql .= " WHERE node.{$config['parent']} = {$parentId}";
             $dql .= " AND node.{$config['left']} {$sign} {$left}";
@@ -295,10 +294,10 @@ class NestedTreeRepository extends AbstractTreeRepository
         }
         return $result;
     }
-    
+
     /**
      * Move the node down in the same level
-     * 
+     *
      * @param object $node
      * @param mixed $number
      *         integer - number of positions to shift
@@ -329,10 +328,10 @@ class NestedTreeRepository extends AbstractTreeRepository
         }
         return $result;
     }
-    
+
     /**
      * Move the node up in the same level
-     * 
+     *
      * @param object $node
      * @param mixed $number
      *         integer - number of positions to shift
@@ -363,10 +362,10 @@ class NestedTreeRepository extends AbstractTreeRepository
         }
         return $result;
     }
-    
+
     /**
      * Removes given $node from the tree and reparents its descendants
-     * 
+     *
      * @param object $node
      * @param bool $autoFlush - flush after removing
      * @throws RuntimeException - if something fails in transaction
@@ -379,7 +378,7 @@ class NestedTreeRepository extends AbstractTreeRepository
             $config = $this->listener->getConfiguration($this->_em, $meta->name);
             $right = $meta->getReflectionProperty($config['right'])->getValue($node);
             $left = $meta->getReflectionProperty($config['left'])->getValue($node);
-                
+
             if ($right == $left + 1) {
                 $this->_em->remove($node);
                 $autoFlush && $this->_em->flush();
@@ -395,7 +394,7 @@ class NestedTreeRepository extends AbstractTreeRepository
                 $pk = $meta->getSingleIdentifierFieldName();
                 $parentId = $meta->getReflectionProperty($pk)->getValue($parent);
                 $nodeId = $meta->getReflectionProperty($pk)->getValue($node);
-                
+
                 $dql = "UPDATE {$meta->rootEntityName} node";
                 $dql .= ' SET node.' . $config['parent'] . ' = ' . $parentId;
                 $dql .= ' WHERE node.' . $config['parent'] . ' = ' . $nodeId;
@@ -405,15 +404,15 @@ class NestedTreeRepository extends AbstractTreeRepository
                 }
                 $q = $this->_em->createQuery($dql);
                 $q->getSingleScalarResult();
-                
+
                 $this->listener
                     ->getStrategy($this->_em, $meta->name)
                     ->shiftRangeRL($this->_em, $meta->rootEntityName, $left, $right, -1, $rootId, $rootId, - 1);
-                    
+
                 $this->listener
                     ->getStrategy($this->_em, $meta->name)
                     ->shiftRL($this->_em, $meta->rootEntityName, $right, -2, $rootId);
-                
+
                 $dql = "UPDATE {$meta->rootEntityName} node";
                 $dql .= ' SET node.' . $config['parent'] . ' = NULL,';
                 $dql .= ' node.' . $config['left'] . ' = 0,';
@@ -434,11 +433,11 @@ class NestedTreeRepository extends AbstractTreeRepository
             throw new InvalidArgumentException("Node is not related to this repository");
         }
     }
-    
+
     /**
      * Reorders the sibling nodes and child nodes by given $node,
      * according to the $sortByField and $direction specified
-     * 
+     *
      * @param object $node - from which node to start reordering the tree
      * @param string $sortByField - field name to sort by
      * @param string $direction - sort direction : "ASC" or "DESC"
@@ -453,7 +452,7 @@ class NestedTreeRepository extends AbstractTreeRepository
             if ($verify && is_array($this->verify())) {
                 return false;
             }
-                   
+
             $nodes = $this->children($node, true, $sortByField, $direction);
             foreach ($nodes as $node) {
                 // this is overhead but had to be refreshed
@@ -471,23 +470,23 @@ class NestedTreeRepository extends AbstractTreeRepository
             throw new InvalidArgumentException("Node is not related to this repository");
         }
     }
-    
+
     /**
      * Verifies that current tree is valid.
      * If any error is detected it will return an array
      * with a list of errors found on tree
-     * 
+     *
      * @return mixed
      *         boolean - true on success
      *         array - error list on failure
      */
     public function verify()
-    {        
+    {
         if (!$this->childCount()) {
             return true; // tree is empty
         }
-        
-        $errors = array();       
+
+        $errors = array();
         $meta = $this->getClassMetadata();
         $config = $this->listener->getConfiguration($this->_em, $meta->name);
         if (isset($config['root'])) {
@@ -498,13 +497,13 @@ class NestedTreeRepository extends AbstractTreeRepository
         } else {
             $this->verifyTree($errors);
         }
-        
+
         return $errors ?: true;
     }
-    
+
     /**
      * Tries to recover the tree
-     * 
+     *
      * @todo implement
      * @throws RuntimeException - if something fails in transaction
      * @return void
@@ -515,7 +514,7 @@ class NestedTreeRepository extends AbstractTreeRepository
             return;
         }
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -523,11 +522,11 @@ class NestedTreeRepository extends AbstractTreeRepository
     {
         return $this->listener->getStrategy($this->_em, $this->getClassMetadata()->name)->getName() === Strategy::NESTED;
     }
-    
+
     /**
      * Collect errors on given tree if
      * where are any
-     * 
+     *
      * @param array $errors
      * @param object $root
      * @return void
@@ -536,10 +535,10 @@ class NestedTreeRepository extends AbstractTreeRepository
     {
         $meta = $this->getClassMetadata();
         $config = $this->listener->getConfiguration($this->_em, $meta->name);
-        
+
         $identifier = $meta->getSingleIdentifierFieldName();
         $rootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($root) : null;
-        
+
         $dql = "SELECT MIN(node.{$config['left']}) FROM {$meta->rootEntityName} node";
         if ($root) {
             $dql .= " WHERE node.{$config['root']} = {$rootId}";
@@ -562,7 +561,7 @@ class NestedTreeRepository extends AbstractTreeRepository
                 }
             }
         }
-        
+
         // check for missing parents
         $dql = "SELECT node FROM {$meta->rootEntityName} node";
         $dql .= " LEFT JOIN node.{$config['parent']} parent";
@@ -578,7 +577,7 @@ class NestedTreeRepository extends AbstractTreeRepository
             }
             return; // loading broken relation can cause infinite loop
         }
-        
+
         $dql = "SELECT node FROM {$meta->rootEntityName} node";
         $dql .= " WHERE node.{$config['right']} < node.{$config['left']}";
         if ($root) {
@@ -587,19 +586,19 @@ class NestedTreeRepository extends AbstractTreeRepository
         $result = $this->_em->createQuery($dql)
             ->setMaxResults(1)
             ->getResult(Query::HYDRATE_ARRAY);
-        $node = count($result) ? array_shift($result) : null; 
-        
+        $node = count($result) ? array_shift($result) : null;
+
         if ($node) {
             $id = $node[$identifier];
             $errors[] = "node [{$id}], left is greater than right" . ($root ? ' on tree root: ' . $rootId : '');
         }
-        
+
         $dql = "SELECT node FROM {$meta->rootEntityName} node";
         if ($root) {
             $dql .= " WHERE node.{$config['root']} = {$rootId}";
         }
         $nodes = $this->_em->createQuery($dql)->getResult(Query::HYDRATE_OBJECT);
-        
+
         foreach ($nodes as $node) {
             $right = $meta->getReflectionProperty($config['right'])->getValue($node);
             $left = $meta->getReflectionProperty($config['left'])->getValue($node);

+ 3 - 3
lib/Gedmo/Tree/Mapping/Driver/Annotation.php

@@ -55,8 +55,8 @@ class Annotation implements Driver
      * Annotation to specify closure tree class
      */
     const ANNOTATION_CLOSURE = 'Gedmo\Tree\Mapping\TreeClosure';
-	
-	/**
+    
+    /**
      * Annotation to mark field as child count
      */
     const ANNOTATION_CHILD_COUNT = 'Gedmo\Tree\Mapping\TreeChildCount';
@@ -233,7 +233,7 @@ class Annotation implements Driver
         }
     }
     
-	/**
+    /**
      * Validates metadata for closure type tree
      * 
      * @param ClassMetadata $meta

+ 1 - 1
lib/Gedmo/Tree/Mapping/Driver/Yaml.php

@@ -177,7 +177,7 @@ class Yaml extends File implements Driver
         }
     }
     
-	/**
+    /**
      * Validates metadata for closure type tree
      * 
      * @param ClassMetadata $meta

+ 23 - 0
lib/Gedmo/Tree/Mapping/Event/Adapter/ODM.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Gedmo\Tree\Mapping\Event\Adapter;
+
+use Gedmo\Mapping\Event\Adapter\ODM as BaseAdapterODM;
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
+use Doctrine\ODM\MongoDB\Cursor;
+
+/**
+ * Doctrine event adapter for ODM adapted
+ * for Tree behavior
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo\Tree\Mapping\Event\Adapter
+ * @subpackage ODM
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+final class ODM extends BaseAdapterODM
+{
+    // Nothing specific yet
+}

+ 23 - 0
lib/Gedmo/Tree/Mapping/Event/Adapter/ORM.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Gedmo\Tree\Mapping\Event\Adapter;
+
+use Gedmo\Mapping\Event\Adapter\ORM as BaseAdapterORM;
+use Doctrine\ORM\EntityManager;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\ORM\Query;
+
+/**
+ * Doctrine event adapter for ORM adapted
+ * for Tree behavior
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo\Tree\Mapping\Event\Adapter
+ * @subpackage ORM
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+final class ORM extends BaseAdapterORM
+{
+    // Nothing specific yet
+}

+ 4 - 4
lib/Gedmo/Tree/Node.php

@@ -6,7 +6,7 @@ namespace Gedmo\Tree;
  * This interface is not necessary but can be implemented for
  * Entities which in some cases needs to be identified as
  * Tree Node
- * 
+ *
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Tree
  * @subpackage Node
@@ -16,19 +16,19 @@ namespace Gedmo\Tree;
 interface Node
 {
     // use now annotations instead of predifined methods, this interface is not necessary
-    
+
     /**
      * @gedmo:TreeLeft
      * to mark the field as "tree left" use property annotation @gedmo:TreeLeft
      * it will use this field to store tree left value
      */
-    
+
     /**
      * @gedmo:TreeRight
      * to mark the field as "tree right" use property annotation @gedmo:TreeRight
      * it will use this field to store tree right value
      */
-    
+
     /**
      * @gedmo:TreeParent
      * in every tree there should be link to parent. To identify a relation

+ 17 - 17
lib/Gedmo/Tree/Strategy.php

@@ -8,66 +8,66 @@ interface Strategy
      * NestedSet strategy
      */
     const NESTED = 'nested';
-    
+
     /**
      * Closure strategy
      */
     const CLOSURE = 'closure';
-    
+
     /**
      * Get the name of strategy
-     * 
+     *
      * @return string
      */
     function getName();
-    
+
     /**
      * Initialize strategy with tree listener
-     * 
-     * @param AbstractTreeListener $listener
+     *
+     * @param TreeListener $listener
      * @return void
      */
-    function __construct(AbstractTreeListener $listener);
-    
+    function __construct(TreeListener $listener);
+
     /**
      * Operations on tree node updates
-     * 
+     *
      * @param object $om - object manager
      * @param object $object - node
      * @return void
      */
     function processScheduledUpdate($om, $object);
-    
+
     /**
      * Operations on tree node deletions
-     * 
+     *
      * @param object $om - object manager
      * @param object $object - node
      * @return void
      */
     function processScheduledDelete($om, $object);
-    
+
     /**
      * Operations on tree node persist
-     * 
+     *
      * @param object $om - object manager
      * @param object $object - node
      * @return void
      */
     function processPrePersist($om, $object);
-    
+
     /**
      * Operations on tree node insertions
-     * 
+     *
      * @param object $om - object manager
      * @param object $object - node
      * @return void
      */
     function processPostPersist($om, $object);
-    
+
     /**
      * Operations on the end of flush process
-     * 
+     *
      * @param object $om - object manager
      * @return void
      */

+ 61 - 61
lib/Gedmo/Tree/Strategy/ORM/Closure.php

@@ -5,12 +5,12 @@ namespace Gedmo\Tree\Strategy\ORM;
 use Gedmo\Tree\Strategy,
     Doctrine\ORM\EntityManager,
     Doctrine\ORM\Proxy\Proxy,
-    Gedmo\Tree\AbstractTreeListener;
+    Gedmo\Tree\TreeListener;
 
 /**
  * This strategy makes tree act like
  * a closure table.
- * 
+ *
  * @author Gustavo Adrian <comfortablynumb84@gmail.com>
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Tree.Strategy.ORM
@@ -19,38 +19,38 @@ use Gedmo\Tree\Strategy,
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
 class Closure implements Strategy
-{   
+{
     /**
      * TreeListener
-     * 
+     *
      * @var AbstractTreeListener
      */
     protected $listener = null;
-    
+
     /**
      * List of pending Nodes, which needs to
      * be post processed because of having a parent Node
      * which requires some additional calculations
-     * 
+     *
      * @var array
      */
     protected $pendingChildNodeInserts = array();
-    
+
     /**
      * List of pending Nodes to remove
-     * 
+     *
      * @var array
      */
     protected $pendingNodesForRemove = array();
-    
+
     /**
      * {@inheritdoc}
      */
-    public function __construct(AbstractTreeListener $listener)
+    public function __construct(TreeListener $listener)
     {
         $this->listener = $listener;
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -58,33 +58,33 @@ class Closure implements Strategy
     {
         return Strategy::CLOSURE;
     }
-    
+
     /**
      * {@inheritdoc}
      */
     public function processPrePersist($em, $entity)
     {
         $this->pendingChildNodeInserts[] = $entity;
-        
+
         $meta = $em->getClassMetadata(get_class($entity));
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         if (isset( $config['childCount'])) {
             // We set by default 0 on insertions for childCount field
             $meta->getReflectionProperty($config['childCount'])->setValue($entity, 0);
         }
     }
-    
+
     /**
      * {@inheritdoc}
      */
     public function processPostPersist($em, $entity)
-    {        
+    {
         if (count($this->pendingChildNodeInserts)) {
             while ($e = array_shift($this->pendingChildNodeInserts)) {
                 $this->insertNode($em, $e);
             }
-            
+
             // If "childCount" property is in the schema, we recalculate child count of all entities
             $meta = $em->getClassMetadata(get_class($entity));
             $config = $this->listener->getConfiguration($em, $meta->name);
@@ -93,10 +93,10 @@ class Closure implements Strategy
             }
         }
     }
-    
+
     /**
      * Insert node and closures
-     * 
+     *
      * @param EntityManager $em
      * @param object $entity
      * @param bool $addNodeChildrenToAncestors
@@ -114,7 +114,7 @@ class Closure implements Strategy
         $entries = array();
         $childrenIDs = array();
         $ancestorsIDs = array();
-        
+
         // If node has children it means it already has a self referencing row, so we skip its insertion
         if ($addNodeChildrenToAncestors === false) {
             $entries[] = array(
@@ -123,15 +123,15 @@ class Closure implements Strategy
                 'depth' => 0
             );
         }
-        
+
         $parent = $meta->getReflectionProperty($config['parent'])->getValue($entity);
-        
+
         if ($parent) {
             $parentId = $meta->getReflectionProperty($identifier)->getValue($parent);
             $dql = "SELECT c.ancestor, c.depth FROM {$closureMeta->name} c";
             $dql .= " WHERE c.descendant = {$parentId}";
             $ancestors = $em->createQuery($dql)->getArrayResult();
-            
+
             foreach ($ancestors as $ancestor) {
                 $entries[] = array(
                     'ancestor' => $ancestor['ancestor'],
@@ -139,13 +139,13 @@ class Closure implements Strategy
                     'depth' => $ancestor['depth'] + 1
                 );
                 $ancestorsIDs[] = $ancestor['ancestor'];
-                
+
                 if ($addNodeChildrenToAncestors === true) {
                     $dql = "SELECT c.descendant, c.depth FROM {$closureMeta->name} c";
                     $dql .= " WHERE c.ancestor = {$id} AND c.ancestor != c.descendant";
                     $children = $em->createQuery($dql)
                         ->getArrayResult();
-                    
+
                     foreach ($children as $child) {
                         $entries[] = array(
                             'ancestor' => $ancestor['ancestor'],
@@ -155,16 +155,16 @@ class Closure implements Strategy
                         $childrenIDs[] = $child['descendant'];
                     }
                 }
-            } 
+            }
         }
-        
+
         foreach ($entries as $closure) {
             if (!$em->getConnection()->insert($closureTable, $closure)) {
                 throw new \Gedmo\Exception\RuntimeException('Failed to insert new Closure record');
             }
         }
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -178,16 +178,16 @@ class Closure implements Strategy
         if (array_key_exists($config['parent'], $changeSet)) {
             $this->updateNode($em, $entity, $changeSet[$config['parent']]);
         }
-        
+
         // If "childCount" property is in the schema, we recalculate child count of all entities
         if (isset($config['childCount'])) {
             $this->recalculateChildCountForEntities($em, get_class($entity));
         }
     }
-    
+
     /**
      * Update node and closures
-     * 
+     *
      * @param EntityManager $em
      * @param object $entity
      * @param array $change - changeset of parent
@@ -200,32 +200,32 @@ class Closure implements Strategy
         $oldParent = $change[0];
         $nodeId = $this->extractIdentifier($em, $entity);
         $table = $closureMeta->getTableName();
-        
+
         if ($oldParent) {
             $this->removeClosurePathsOfNodeID($em, $table, $nodeId);
             $this->insertNode($em, $entity, true);
         }
     }
-    
+
     /**
      * {@inheritdoc}
      */
     public function processScheduledDelete($em, $entity)
     {
         $this->removeNode($em, $entity);
-        
+
         // If "childCount" property is in the schema, we recalculate child count of all entities
         $meta = $em->getClassMetadata(get_class($entity));
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         if (isset($config['childCount'])) {
             $this->recalculateChildCountForEntities($em, get_class( $entity ));
         }
     }
-	
+
     /**
      * Remove node and associated closures
-     * 
+     *
      * @param EntityManager $em
      * @param object $entity
      * @param bool $maintainSelfReferencingRow
@@ -237,45 +237,45 @@ class Closure implements Strategy
         $config = $this->listener->getConfiguration($em, $meta->name);
         $closureMeta = $em->getClassMetadata($config['closure']);
         $id = $this->extractIdentifier($em, $entity);
-        
+
         $this->removeClosurePathsOfNodeID($em, $closureMeta->getTableName(), $id, $maintainSelfReferencingRow, $maintainSelfReferencingRowOfChildren);
-	}
-    
-	/**
-	 * Remove closures for node $nodeId
-	 * 
-	 * @param EntityManager $em
-	 * @param string $table
-	 * @param integer $nodeId
-	 * @param bool $maintainSelfReferencingRow
-	 * @param bool $maintainSelfReferencingRowOfChildren
-	 * @throws \Gedmo\Exception\RuntimeException - if deletion of closures fails
-	 */
+    }
+
+    /**
+     * Remove closures for node $nodeId
+     *
+     * @param EntityManager $em
+     * @param string $table
+     * @param integer $nodeId
+     * @param bool $maintainSelfReferencingRow
+     * @param bool $maintainSelfReferencingRowOfChildren
+     * @throws \Gedmo\Exception\RuntimeException - if deletion of closures fails
+     */
     public function removeClosurePathsOfNodeID(EntityManager $em, $table, $nodeId, $maintainSelfReferencingRow = true, $maintainSelfReferencingRowOfChildren = true)
     {
         $subquery = "SELECT c1.id FROM {$table} c1 ";
         $subquery .= "WHERE c1.descendant IN (SELECT c2.descendant FROM {$table} c2 WHERE c2.ancestor = :id) ";
         $subquery .= "AND (c1.ancestor IN (SELECT c3.ancestor FROM {$table} c3 WHERE c3.descendant = :id ";
-        
+
         if ($maintainSelfReferencingRow === true) {
             $subquery .= "AND c3.descendant != c3.ancestor ";
         }
-        
+
         if ( $maintainSelfReferencingRowOfChildren === false) {
             $subquery .= " OR c1.descendant = c1.ancestor ";
         }
-        
+
         $subquery .= " )) ";
         $subquery = "DELETE FROM {$table} WHERE {$table}.id IN (SELECT temp_table.id FROM ({$subquery}) temp_table)";
-        
+
         if (!$em->getConnection()->executeQuery($subquery, array('id' => $nodeId))) {
             throw new \Gedmo\Exception\RuntimeException('Failed to delete old Closure records');
         }
     }
-    
+
     /**
      * Childcount recalculation
-     * 
+     *
      * @param EntityManager $em
      * @param string $entityClass
      * @throws \Gedmo\Exception\RuntimeException - if update fails
@@ -290,18 +290,18 @@ class Closure implements Strategy
         $closureMeta = $em->getClassMetadata($config['closure']);
         $entityTable = $meta->getTableName();
         $closureTable = $closureMeta->getTableName();
-        
+
         $subquery = "(SELECT COUNT( c2.descendant ) FROM {$closureTable} c2 WHERE c2.ancestor = c1.{$entityIdentifierField} AND c2.ancestor != c2.descendant)";
         $sql = "UPDATE {$entityTable} c1 SET c1.{$childCountField} = {$subquery}";
-        
+
         if (!$em->getConnection()->executeQuery($sql)) {
             throw new \Gedmo\Exception\RuntimeException('Failed to update child count field of entities');
         }
     }
-    
+
     /**
      * Extracts identifiers from object or proxy
-     * 
+     *
      * @param EntityManager $em
      * @param object $entity
      * @param bool $single
@@ -323,7 +323,7 @@ class Closure implements Strategy
         }
         return $id;
     }
-    
+
     /**
      * {@inheritdoc}
      */

+ 70 - 70
lib/Gedmo/Tree/Strategy/ORM/Nested.php

@@ -4,16 +4,16 @@ namespace Gedmo\Tree\Strategy\ORM;
 
 use Gedmo\Tree\Strategy,
     Doctrine\ORM\EntityManager,
-    Gedmo\Tree\AbstractTreeListener,
+    Gedmo\Tree\TreeListener,
     Doctrine\ORM\Query;
 
 /**
  * This strategy makes tree act like
  * nested set.
- * 
+ *
  * This behavior can inpact the performance of your application
  * since nested set trees are slow on inserts and updates.
- * 
+ *
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Tree.Strategy.ORM
  * @subpackage Nested
@@ -26,72 +26,72 @@ class Nested implements Strategy
      * Previous sibling position
      */
     const PREV_SIBLING = 'prevSibling';
-    
+
     /**
      * Next sibling position
      */
     const NEXT_SIBLING = 'nextSibling';
-    
+
     /**
      * Last child position
      */
     const LAST_CHILD = 'lastChild';
-    
+
     /**
      * First child position
      */
     const FIRST_CHILD = 'firstChild';
-    
+
     /**
      * TreeListener
-     * 
+     *
      * @var AbstractTreeListener
      */
     protected $listener = null;
-    
+
     /**
      * The max number of "right" field of the
      * tree in case few root nodes will be persisted
      * on one flush for node classes
-     * 
+     *
      * @var array
      */
     private $treeEdges = array();
-    
+
     /**
      * List of pending Nodes, which needs to
      * be post processed because of having a parent Node
      * which requires some additional calculations
-     * 
+     *
      * @var array
      */
     private $pendingChildNodeInserts = array();
-    
+
     /**
      * List of persisted nodes for specific
      * class to know when to process pending
      * inserts
-     * 
+     *
      * @var array
      */
     private $persistedNodes = array();
-    
+
     /**
      * Number of updates for specific
      * classes to know if refresh is necessary
-     * 
+     *
      * @var array
      */
     private $updatesOnNodeClasses = array();
-    
+
     /**
      * {@inheritdoc}
      */
-    public function __construct(AbstractTreeListener $listener)
+    public function __construct(TreeListener $listener)
     {
         $this->listener = $listener;
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -99,7 +99,7 @@ class Nested implements Strategy
     {
         return Strategy::NESTED;
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -108,7 +108,7 @@ class Nested implements Strategy
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
         $uow = $em->getUnitOfWork();
-        
+
         $changeSet = $uow->getEntityChangeSet($node);
         if (isset($config['root']) && isset($changeSet[$config['root']])) {
             throw new \Gedmo\Exception\UnexpectedValueException("Root cannot be changed manualy, change parent instead");
@@ -119,18 +119,18 @@ class Nested implements Strategy
             $this->updateNode($em, $node, $changeSet[$config['parent']][1]);
         }
     }
-    
+
     /**
      * {@inheritdoc}
      */
-    public function processPostPersist($em, $node) 
+    public function processPostPersist($em, $node)
     {
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
 
         if (isset($config['root'])) {
             $parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
-        
+
             $identifierField = $meta->getSingleIdentifierFieldName();
             $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
             if ($parent) {
@@ -145,7 +145,7 @@ class Nested implements Strategy
             $em->createQuery($dql)->getSingleScalarResult();
         }
         unset($this->persistedNodes[spl_object_hash($node)]);
-        
+
         if (!$this->persistedNodes && $this->pendingChildNodeInserts) {
             $pendingChildNodeInserts = $this->pendingChildNodeInserts;
             foreach ($pendingChildNodeInserts as $class => &$nodes) {
@@ -156,7 +156,7 @@ class Nested implements Strategy
             $this->pendingChildNodeInserts = array();
         }
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -165,10 +165,10 @@ class Nested implements Strategy
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
         $uow = $em->getUnitOfWork();
-        
+
         $leftValue = $meta->getReflectionProperty($config['left'])->getValue($node);
         $rightValue = $meta->getReflectionProperty($config['right'])->getValue($node);
-        
+
         if (!$leftValue || !$rightValue) {
             return;
         }
@@ -189,10 +189,10 @@ class Nested implements Strategy
                 $uow->scheduleForDelete($removalNode);
             }
         }
-        
+
         $this->shiftRL($em, $meta->rootEntityName, $rightValue + 1, -$diff, $rootId);
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -202,7 +202,7 @@ class Nested implements Strategy
         $this->treeEdges = array();
         $this->updatesOnNodeClasses = array();
     }
-    
+
     /**
      * {@inheritdoc}
      */
@@ -210,7 +210,7 @@ class Nested implements Strategy
     {
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         $parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
         if ($parent === null) {
             $this->prepareRoot($em, $node);
@@ -230,11 +230,11 @@ class Nested implements Strategy
         }
         $this->persistedNodes[spl_object_hash($node)] = null;
     }
-    
+
     /**
      * Insert a node which requires
      * parent synchronization
-     * 
+     *
      * @param EntityManager $em
      * @param object $node
      * @return void
@@ -243,18 +243,18 @@ class Nested implements Strategy
     {
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         $parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
         $identifierField = $meta->getSingleIdentifierFieldName();
         $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
-        
+
         if (isset($this->pendingChildNodeInserts[$meta->name]) && count($this->pendingChildNodeInserts[$meta->name]) > 1) {
             $em->refresh($parent);
         }
         $rootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($parent) : null;
         $parentRight = $meta->getReflectionProperty($config['right'])->getValue($parent);
         $this->shiftRL($em, $meta->rootEntityName, $parentRight, 2, $rootId);
-        
+
         $meta->getReflectionProperty($config['left'])->setValue($node, $parentRight);
         $meta->getReflectionProperty($config['right'])->setValue($node, $parentRight + 1);
         $dql = "UPDATE {$meta->rootEntityName} node";
@@ -263,11 +263,11 @@ class Nested implements Strategy
         $dql .= " WHERE node.{$identifierField} = {$nodeId}";
         $em->createQuery($dql)->getSingleScalarResult();
     }
-    
+
     /**
      * Update the $node with a diferent $parent
      * destination
-     * 
+     *
      * @todo consider $position configurable through listener
      * @param EntityManager $em
      * @param object $node - target node
@@ -280,18 +280,18 @@ class Nested implements Strategy
     {
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         // if there is more than one update, need to refresh node
         if (!isset($this->updatesOnNodeClasses[$meta->name]) || $this->updatesOnNodeClasses[$meta->name] > 1) {
             $em->refresh($node);
         }
         $rootId = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null;
         $identifierField = $meta->getSingleIdentifierFieldName();
-        $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node); 
-        
+        $nodeId = $meta->getReflectionProperty($identifierField)->getValue($node);
+
         $left = $meta->getReflectionProperty($config['left'])->getValue($node);
         $right = $meta->getReflectionProperty($config['right'])->getValue($node);
-        
+
         $level = 0;
         $treeSize = $right - $left + 1;
         $newRootId = null;
@@ -312,15 +312,15 @@ class Nested implements Strategy
                 case self::PREV_SIBLING:
                     $start = $parentLeft;
                     break;
-                    
+
                 case self::NEXT_SIBLING:
                     $start = $parentRight + 1;
                     break;
-                    
+
                 case self::LAST_CHILD:
                     $start = $parentRight;
                     $level++;
-                    
+
                 case self::FIRST_CHILD:
                 default:
                     $start = $parentLeft + 1;
@@ -343,7 +343,7 @@ class Nested implements Strategy
             $start = 1;
             $newRootId = $nodeId;
         }
-        
+
         $diff = $start - $left;
         $qb = $em->createQueryBuilder();
         $qb->update($meta->rootEntityName, 'node');
@@ -356,10 +356,10 @@ class Nested implements Strategy
         if ($treeSize > 2) {
             $levelDiff = isset($config['level']) ? $level - $meta->getReflectionProperty($config['level'])->getValue($node) : null;
             $this->shiftRangeRL(
-                $em, 
-                $meta->rootEntityName, 
-                $left, 
-                $right, 
+                $em,
+                $meta->rootEntityName,
+                $left,
+                $right,
                 $diff,
                 $rootId,
                 $newRootId,
@@ -369,15 +369,15 @@ class Nested implements Strategy
             $qb->set('node.' . $config['left'], $left + $diff);
             $qb->set('node.' . $config['right'], $right + $diff);
         }
-        
+
         $qb->where("node.{$identifierField} = {$nodeId}");
         $qb->getQuery()->getSingleScalarResult();
         $this->shiftRL($em, $meta->rootEntityName, $left, -$treeSize, $rootId);
     }
-    
+
     /**
      * Get the edge of tree
-     * 
+     *
      * @param EntityManager $em
      * @param string $class
      * @param integer $rootId
@@ -387,20 +387,20 @@ class Nested implements Strategy
     {
         $meta = $em->getClassMetadata($class);
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         $dql = "SELECT MAX(node.{$config['right']}) FROM {$meta->rootEntityName} node";
         if (isset($config['root']) && $rootId) {
             $dql .= " WHERE node.{$config['root']} = {$rootId}";
         }
-        
+
         $query = $em->createQuery($dql);
         $right = $query->getSingleScalarResult();
         return intval($right);
     }
-    
+
     /**
      * Shift tree left and right values by delta
-     * 
+     *
      * @param EntityManager $em
      * @param string $class
      * @param integer $first
@@ -408,14 +408,14 @@ class Nested implements Strategy
      * @param integer $rootId
      * @return void
      */
-    public function shiftRL(EntityManager $em, $class, $first, $delta, $rootId = null) 
+    public function shiftRL(EntityManager $em, $class, $first, $delta, $rootId = null)
     {
         $meta = $em->getClassMetadata($class);
         $config = $this->listener->getConfiguration($em, $class);
-        
+
         $sign = ($delta >= 0) ? ' + ' : ' - ';
         $delta = abs($delta);
-        
+
         $dql = "UPDATE {$meta->rootEntityName} node";
         $dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$delta}";
         $dql .= " WHERE node.{$config['left']} >= {$first}";
@@ -424,7 +424,7 @@ class Nested implements Strategy
         }
         $q = $em->createQuery($dql);
         $q->getSingleScalarResult();
-        
+
         $dql = "UPDATE {$meta->rootEntityName} node";
         $dql .= " SET node.{$config['right']} = node.{$config['right']} {$sign} {$delta}";
         $dql .= " WHERE node.{$config['right']} >= {$first}";
@@ -434,11 +434,11 @@ class Nested implements Strategy
         $q = $em->createQuery($dql);
         $q->getSingleScalarResult();
     }
-    
+
     /**
      * Shift range of right and left values on tree
      * depending on tree level diference also
-     * 
+     *
      * @param EntityManager $em
      * @param string $class
      * @param integer $first
@@ -453,12 +453,12 @@ class Nested implements Strategy
     {
         $meta = $em->getClassMetadata($class);
         $config = $this->listener->getConfiguration($em, $class);
-        
+
         $sign = ($delta >= 0) ? ' + ' : ' - ';
         $delta = abs($delta);
         $levelSign = ($levelDelta >= 0) ? ' + ' : ' - ';
         $levelDelta = abs($levelDelta);
-        
+
         $dql = "UPDATE {$meta->rootEntityName} node";
         $dql .= " SET node.{$config['left']} = node.{$config['left']} {$sign} {$delta}";
         $dql .= ", node.{$config['right']} = node.{$config['right']} {$sign} {$delta}";
@@ -476,10 +476,10 @@ class Nested implements Strategy
         $q = $em->createQuery($dql);
         $q->getSingleScalarResult();
     }
-    
+
     /**
      * If Node does not have parent, set it as root
-     * 
+     *
      * @param EntityManager $em
      * @param object $entity
      * @return void
@@ -488,13 +488,13 @@ class Nested implements Strategy
     {
         $meta = $em->getClassMetadata(get_class($node));
         $config = $this->listener->getConfiguration($em, $meta->name);
-        
+
         if (isset($config['root'])) {
             $meta->getReflectionProperty($config['root'])->setValue($node, null);
             $meta->getReflectionProperty($config['left'])->setValue($node, 1);
             $meta->getReflectionProperty($config['right'])->setValue($node, 2);
         } else {
-            $edge = isset($this->treeEdges[$meta->name]) ? 
+            $edge = isset($this->treeEdges[$meta->name]) ?
                 $this->treeEdges[$meta->name] : $this->max($em, $meta->rootEntityName);
             $meta->getReflectionProperty($config['left'])->setValue($node, $edge + 1);
             $meta->getReflectionProperty($config['right'])->setValue($node, $edge + 2);

+ 158 - 35
lib/Gedmo/Tree/TreeListener.php

@@ -2,79 +2,202 @@
 
 namespace Gedmo\Tree;
 
-use Doctrine\ORM\Events,
-    Doctrine\Common\EventArgs;
+use Doctrine\Common\EventArgs,
+    Gedmo\Mapping\MappedEventSubscriber,
+    Doctrine\Common\Persistence\ObjectManager;
 
 /**
  * The tree listener handles the synchronization of
- * tree nodes for entities. Can implement diferent
+ * tree nodes. Can implement diferent
  * strategies on handling the tree.
- * 
+ *
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Tree
  * @subpackage TreeListener
  * @link http://www.gediminasm.org
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
-class TreeListener extends AbstractTreeListener
-{   
+class TreeListener extends MappedEventSubscriber
+{
+    /**
+     * Tree processing strategies for object classes
+     *
+     * @var array
+     */
+    private $strategies = array();
+
+    /**
+     * List of strategy instances
+     *
+     * @var array
+     */
+    private $strategyInstances = array();
+
     /**
      * Specifies the list of events to listen
-     * 
+     *
      * @return array
      */
     public function getSubscribedEvents()
     {
         return array(
-            Events::prePersist,
-            Events::postPersist,
-            Events::preRemove,
-            Events::onFlush,
-            Events::loadClassMetadata
+            'prePersist',
+            'postPersist',
+            'preRemove',
+            'onFlush',
+            'loadClassMetadata'
         );
     }
-    
+
     /**
-     * {@inheritdoc}
+     * Get the used strategy for tree processing
+     *
+     * @param ObjectManager $om
+     * @param string $class
+     * @return Strategy
      */
-    protected function loadStrategy($type)
+    public function getStrategy(ObjectManager $om, $class)
     {
-        $class = $this->getNamespace() . '\Strategy\ORM\\' . ucfirst($type);
-        if (!class_exists($class)) {
-            throw new \Gedmo\Exception\InvalidArgumentException("ORM TreeListener does not support tree type: {$type}");
+        if (!isset($this->strategies[$class])) {
+            $config = $this->getConfiguration($om, $class);
+            if (!$config) {
+                throw new \Gedmo\Exception\UnexpectedValueException("Tree object class: {$class} must have tree metadata at this point");
+            }
+            $managerName = 'UnsupportedManager';
+            if ($om instanceof \Doctrine\ORM\EntityManager) {
+                $managerName = 'ORM';
+            } elseif ($om instanceof \Doctrine\ODM\MongoDB\DocumentManager) {
+                $managerName = 'ODM';
+            }
+            if (!isset($this->strategyInstances[$config['strategy']])) {
+                $class = $this->getNamespace().'\\Strategy\\'.$managerName.'\\'.ucfirst($config['strategy']);
+                if (!class_exists($class)) {
+                    throw new \Gedmo\Exception\InvalidArgumentException($managerName." TreeListener does not support tree type: {$config['strategy']}");
+                }
+                $this->strategyInstances[$config['strategy']] = new $class($this);
+            }
+            $this->strategies[$class] = $config['strategy'];
         }
-        return new $class($this);
+        return $this->strategyInstances[$this->strategies[$class]];
     }
-    
+
     /**
-     * {@inheritdoc}
+     * Looks for Tree objects being updated
+     * for further processing
+     *
+     * @param EventArgs $args
+     * @return void
      */
-    protected function getObjectManager(EventArgs $args)
+    public function onFlush(EventArgs $args)
     {
-        return $args->getEntityManager();
+        $ea = $this->getEventAdapter($args);
+        $om = $ea->getObjectManager();
+        $uow = $om->getUnitOfWork();
+        // check all scheduled updates for TreeNodes
+        $usedClasses = array();
+
+        foreach ($ea->getScheduledObjectUpdates($uow) as $object) {
+            $meta = $om->getClassMetadata(get_class($object));
+            if ($config = $this->getConfiguration($om, $meta->name)) {
+                $usedClasses[$meta->name] = null;
+                $this->getStrategy($om, $meta->name)->processScheduledUpdate($om, $object);
+            }
+        }
+        foreach ($this->getStrategiesUsedForObjects($usedClasses) as $strategy) {
+            $strategy->onFlushEnd($om);
+        }
     }
-    
+
     /**
-     * {@inheritdoc}
+     * Updates tree on Node removal
+     *
+     * @param EventArgs $args
+     * @return void
      */
-    protected function getObject(EventArgs $args)
+    public function preRemove(EventArgs $args)
     {
-        return $args->getEntity();
+        $ea = $this->getEventAdapter($args);
+        $om = $ea->getObjectManager();
+        $object = $ea->getObject();
+        $meta = $om->getClassMetadata(get_class($object));
+
+        if ($config = $this->getConfiguration($om, $meta->name)) {
+            $this->getStrategy($om, $meta->name)->processScheduledDelete($om, $object);
+        }
     }
-    
+
     /**
-     * {@inheritdoc}
+     * Checks for persisted Nodes
+     *
+     * @param EventArgs $args
+     * @return void
      */
-    protected function getObjectChangeSet($uow, $object)
+    public function prePersist(EventArgs $args)
     {
-        return $uow->getEntityChangeSet($object);
+        $ea = $this->getEventAdapter($args);
+        $om = $ea->getObjectManager();
+        $object = $ea->getObject();
+        $meta = $om->getClassMetadata(get_class($object));
+
+        if ($config = $this->getConfiguration($om, $meta->name)) {
+            $this->getStrategy($om, $meta->name)->processPrePersist($om, $object);
+        }
     }
-    
+
     /**
-     * {@inheritdoc}
+     * Checks for pending Nodes to fully synchronize
+     * the tree
+     *
+     * @param EventArgs $args
+     * @return void
      */
-    protected function getScheduledObjectUpdates($uow)
+    public function postPersist(EventArgs $args)
     {
-        return $uow->getScheduledEntityUpdates();
+        $ea = $this->getEventAdapter($args);
+        $om = $ea->getObjectManager();
+        $object = $ea->getObject();
+        $meta = $om->getClassMetadata(get_class($object));
+
+        if ($config = $this->getConfiguration($om, $meta->name)) {
+            $this->getStrategy($om, $meta->name)->processPostPersist($om, $object);
+        }
+    }
+
+    /**
+     * Mapps additional metadata
+     *
+     * @param EventArgs $eventArgs
+     * @return void
+     */
+    public function loadClassMetadata(EventArgs $eventArgs)
+    {
+        $ea = $this->getEventAdapter($eventArgs);
+        $this->loadMetadataForObjectClass($ea->getObjectManager(), $eventArgs->getClassMetadata());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function getNamespace()
+    {
+        return __NAMESPACE__;
+    }
+
+    /**
+     * Get the list of strategy instances used for
+     * given object classes
+     *
+     * @param array $classes
+     * @return array
+     */
+    private function getStrategiesUsedForObjects(array $classes)
+    {
+        $strategies = array();
+        foreach ($classes as $name => $opt) {
+            if (isset($this->strategies[$name]) && !isset($strategies[$this->strategies[$name]])) {
+                $strategies[$this->strategies[$name]] = $this->strategyInstances[$this->strategies[$name]];
+            }
+        }
+        return $strategies;
     }
 }

+ 1 - 1
tests/Gedmo/Tree/Fixture/Closure/Category.php

@@ -23,7 +23,7 @@ class Category
     
     /**
      * @gedmo:TreeParent
-	 * @JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
+     * @JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
      * @ManyToOne(targetEntity="Category", inversedBy="children", cascade={"persist"})
      */
     private $parent;