소스 검색

[Tree - Closure Table] Added update and delete support

Gustavo Adrian 14 년 전
부모
커밋
79d1ec44c6

+ 0 - 0
lib/Gedmo/Mapping/MappedEventSubscriber.php


+ 7 - 2
lib/Gedmo/Tree/Entity/AbstractClosure.php

@@ -8,13 +8,18 @@ namespace Gedmo\Tree\Entity;
 abstract class AbstractClosure
 {
     /**
-     * @Id
+	 * @Id
+	 * @Column(type="integer")
+	 * @GeneratedValue(strategy="IDENTITY")
+	 */
+	private $id;
+	
+	/**
      * @Column(type="integer")
      */
     private $ancestor;
     
     /**
-     * @Id
      * @Column(type="integer")
      */
     private $descendant;

+ 127 - 60
lib/Gedmo/Tree/Strategy/ORM/Closure.php

@@ -40,6 +40,13 @@ class Closure implements Strategy
      * @var array
      */
     protected $pendingChildNodeInserts = array();
+	
+	/**
+     * List of pending Nodes to remove
+     * 
+     * @var array
+     */
+    protected $pendingNodesForRemove = array();
     
     /**
      * {@inheritdoc}
@@ -56,52 +63,8 @@ class Closure implements Strategy
     {
         return Strategy::CLOSURE;
     }
-    
-    /**
-     * {@inheritdoc}
-     */
-    public function processScheduledUpdate($em, $entity)
-    {
-        $entityClass = get_class($entity);
-        $config = $this->listener->getConfiguration($em, $entityClass);
-        $meta = $em->getClassMetadata($entityClass);
-        $uow = $em->getUnitOfWork();
-        $changeSet = $uow->getEntityChangeSet($entity);
-        if (array_key_exists($config['parent'], $changeSet)) {
-            $this->updateNode($em, $entity, $changeSet[$config['parent']]);
-        }
-    }
-    
-    public function updateNode(EntityManager $em, $entity, array $change)
-    {
-        $meta = $em->getClassMetadata(get_class($entity));
-        $config = $this->listener->getConfiguration($em, $meta->name);
-        $closureMeta = $em->getClassMetadata($config['closure']);
-        
-        $oldParent = $change[0];
-        $nodeId = $this->extractIdentifier($em, $entity);
-        //$oldParentId = $this->extractIdentifier($em, $oldParent);
-        
-        $table = $closureMeta->getTableName();
-        if ($oldParent) {
-            // requires single identifier in closure table 
-            $subselect = "SELECT c2.id FROM {$table} c1";
-            $subselect .= " INNER JOIN {$table} c2 ON c1.descendant = c2.descendant";
-            $subselect .= " WHERE c1.ancestor = :id";
-            $subselect .= " AND c2.depth > c1.depth";
-            
-            $sql = "DELETE FROM {$table}";
-            $sql .= " WHERE id IN({$subselect})";
-            if (!$em->getConnection()->executeQuery($sql, array('id' => $nodeId))) {
-                throw new \Gedmo\Exception\RuntimeException('Failed to delete old Closure records');
-            }
-        }
-        
-        //\Doctrine\Common\Util\Debug::dump($oldParent);
-        //die();
-    }
-    
-    /**
+	
+	/**
      * {@inheritdoc}
      */
     public function processPrePersist($em, $entity)
@@ -127,7 +90,7 @@ class Closure implements Strategy
         }
     }
     
-    public function insertNode(EntityManager $em, $entity)
+    public function insertNode(EntityManager $em, $entity, $addNodeChildrenToAncestors = false )
     {
         $meta = $em->getClassMetadata(get_class($entity));
         $config = $this->listener->getConfiguration($em, $meta->name);
@@ -135,13 +98,19 @@ class Closure implements Strategy
         $id = $meta->getReflectionProperty($identifier)->getValue($entity);
         $closureMeta = $em->getClassMetadata($config['closure']);
         $entries = array();
-        $entries[] = array(
-        	'ancestor' => $id,
-            'descendant' => $id,
-            'depth' => 0
-        );
-
+		
+		// If node has children it means it already has a self referencing row, so we skip the insert of it
+		if ( $addNodeChildrenToAncestors === false )
+		{
+			$entries[] = array(
+				'ancestor' => $id,
+				'descendant' => $id,
+				'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";
@@ -154,7 +123,24 @@ class Closure implements Strategy
                     'descendant' => $id,
                     'depth' => $ancestor['depth'] + 1
                 );
-            }
+				
+				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' ],
+							'descendant'	=> $child[ 'descendant' ],
+							'depth'			=> $child[ 'depth' ] + 1
+						);
+					}
+				}
+            }	
         }
         
         $table = $closureMeta->getTableName();
@@ -165,6 +151,93 @@ class Closure implements Strategy
         }
     }
     
+    /**
+     * {@inheritdoc}
+     */
+    public function processScheduledUpdate($em, $entity)
+    {
+        $entityClass = get_class($entity);
+        $config = $this->listener->getConfiguration($em, $entityClass);
+        $meta = $em->getClassMetadata($entityClass);
+        $uow = $em->getUnitOfWork();
+        $changeSet = $uow->getEntityChangeSet($entity);
+        if (array_key_exists($config['parent'], $changeSet)) {
+            $this->updateNode($em, $entity, $changeSet[$config['parent']]);
+        }
+    }
+    
+    public function updateNode(EntityManager $em, $entity, array $change)
+    {
+        $meta = $em->getClassMetadata(get_class($entity));
+        $config = $this->listener->getConfiguration($em, $meta->name);
+        $closureMeta = $em->getClassMetadata($config['closure']);
+        $oldParent = $change[0];
+        $nodeId = $this->extractIdentifier($em, $entity);
+        //$oldParentId = $this->extractIdentifier($em, $oldParent);
+        
+        $table = $closureMeta->getTableName();
+        if ($oldParent) {
+            $this->removeClosurePathsOfNodeID( $em, $table, $nodeId );
+			
+			$this->insertNode( $em, $entity, true );
+        }
+        
+        //\Doctrine\Common\Util\Debug::dump($oldParent);
+        //die();
+    }
+	
+	/**
+     * {@inheritdoc}
+     */
+    public function processScheduledDelete($em, $entity)
+    {
+		$this->removeNode( $em, $entity );
+	}
+	
+	public function postRemove( EventArgs $args )
+	{
+		$em = $this->getObjectManager($args);
+		
+		foreach ( $this->pendingNodesForRemove as $node )
+		{
+			$this->removeNode( $em, $node );
+		}
+	}
+	
+	public function removeNode( EntityManager $em, $entity, $maintainSelfReferencingRow = false, $maintainSelfReferencingRowOfChildren = false )
+	{
+		$meta 			= $em->getClassMetadata( get_class( $entity ) );
+        $config 		= $this->listener->getConfiguration( $em, $meta->name );
+        $closureMeta 	= $em->getClassMetadata( $config[ 'closure' ] );
+		
+		$this->removeClosurePathsOfNodeID( $em, $closureMeta->getTableName(), $entity->getId(), $maintainSelfReferencingRow, $maintainSelfReferencingRowOfChildren );
+	}
+	
+	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');
+		}
+	}
+    
     private function extractIdentifier($em, $entity, $single = true)
     {
         if ($entity instanceof Proxy) {
@@ -182,12 +255,6 @@ class Closure implements Strategy
         return $id;
     }
     
-    /**
-     * {@inheritdoc}
-     */
-    public function processScheduledDelete($em, $entity)
-    {}
-    
     /**
      * {@inheritdoc}
      */

+ 35 - 0
tests/Gedmo/Timestampable/Proxy/TimestampableFixtureArticleProxy.php

@@ -69,21 +69,56 @@ class TimestampableFixtureArticleProxy extends \Timestampable\Fixture\Article im
         return parent::getCreated();
     }
 
+    public function setCreated(\DateTime $created)
+    {
+        $this->_load();
+        return parent::setCreated($created);
+    }
+
     public function getPublished()
     {
         $this->_load();
         return parent::getPublished();
     }
 
+    public function setPublished(\DateTime $published)
+    {
+        $this->_load();
+        return parent::setPublished($published);
+    }
+
     public function getUpdated()
     {
         $this->_load();
         return parent::getUpdated();
     }
 
+    public function setUpdated(\DateTime $updated)
+    {
+        $this->_load();
+        return parent::setUpdated($updated);
+    }
+
 
     public function __sleep()
     {
         return array('__isInitialized__', 'id', 'title', 'comments', 'created', 'updated', 'published', 'type');
     }
+
+    public function __clone()
+    {
+        if (!$this->__isInitialized__ && $this->_entityPersister) {
+            $this->__isInitialized__ = true;
+            $class = $this->_entityPersister->getClassMetadata();
+            $original = $this->_entityPersister->load($this->_identifier);
+            if ($original === null) {
+                throw new \Doctrine\ORM\EntityNotFoundException();
+            }
+            foreach ($class->reflFields AS $field => $reflProperty) {
+                $reflProperty->setValue($this, $reflProperty->getValue($original));
+            }
+            unset($this->_entityPersister, $this->_identifier);
+        }
+        
+    }
 }

+ 17 - 0
tests/Gedmo/Timestampable/Proxy/TimestampableFixtureTypeProxy.php

@@ -50,4 +50,21 @@ class TimestampableFixtureTypeProxy extends \Timestampable\Fixture\Type implemen
     {
         return array('__isInitialized__', 'id', 'title', 'articles');
     }
+
+    public function __clone()
+    {
+        if (!$this->__isInitialized__ && $this->_entityPersister) {
+            $this->__isInitialized__ = true;
+            $class = $this->_entityPersister->getClassMetadata();
+            $original = $this->_entityPersister->load($this->_identifier);
+            if ($original === null) {
+                throw new \Doctrine\ORM\EntityNotFoundException();
+            }
+            foreach ($class->reflFields AS $field => $reflProperty) {
+                $reflProperty->setValue($this, $reflProperty->getValue($original));
+            }
+            unset($this->_entityPersister, $this->_identifier);
+        }
+        
+    }
 }

+ 199 - 28
tests/Gedmo/Tree/ClosureTreeTest.php

@@ -29,6 +29,13 @@ class ClosureTreeTest extends \PHPUnit_Framework_TestCase
      * @var QueryAnalyzer
      */
     private $analyzer;
+	
+	private $food;
+	private $sports;
+	private $fruits;
+	private $vegitables;
+	private $carrots;
+	private $potatoes;
 
     public function setUp()
     {        
@@ -39,88 +46,252 @@ class ClosureTreeTest extends \PHPUnit_Framework_TestCase
         $config->setProxyNamespace('Gedmo\Tree\Proxies');
         $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver());
 
-        $conn = array(
+        /*$conn = array(
             'driver' => 'pdo_sqlite',
             'memory' => true,
-        );
+        );*/
         
-        /*$conn = array(
+        $conn = array(
             'driver' => 'pdo_mysql',
             'host' => '127.0.0.1',
-            'dbname' => 'test',
+            'dbname' => 'closure',
             'user' => 'root',
-            'password' => 'nimda'
-        );*/
+            'password' => 'B9jt12h0'
+        );
         
         $evm = new \Doctrine\Common\EventManager();
         $treeListener = new TreeListener(TreeListener::TYPE_CLOSURE);
         $evm->addEventSubscriber($treeListener);
         $this->em = \Doctrine\ORM\EntityManager::create($conn, $config, $evm);
         
-        $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($this->em);
-        $schemaTool->dropSchema(array());
-        $schemaTool->createSchema(array(
+		$schema	= array(
             $this->em->getClassMetadata(self::TEST_BASE_CLOSURE_CLASS),
             $this->em->getClassMetadata(self::TEST_CLOSURE_CLASS),
             $this->em->getClassMetadata(self::TEST_ENTITY_CLASS),
-        ));
+        );
+        $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($this->em);
+        $schemaTool->dropSchema( $schema );
+        $schemaTool->createSchema( $schema );
         
         $this->analyzer = new QueryAnalyzer(
             $this->em->getConnection()->getDatabasePlatform()
         );
         $config->setSQLLogger($this->analyzer);
-        
-        $this->populate();
+		
+		$this->populate();
     }
     
     public function tearDown()
     {
         $this->analyzer->dumpResult();
+		$this->em->clear();
     }
     
-    public function testTheTree()
+    public function test_insertNodes_verifyClosurePaths()
     {        
-        $repo = $this->em->getRepository(self::TEST_ENTITY_CLASS);
-        $node = $repo->findOneByTitle('Vegitables');
-        $node->setParent($repo->findOneByTitle('Fruits'));
-        
-        //$this->em->persist($node);
-        //$this->em->flush();
+		// We check the inserted nodes fields from the closure table
+		$repo 	= $this->em->getRepository(self::TEST_CLOSURE_CLASS);
+        $rows	= $this->em->createQuery( sprintf( 'SELECT c FROM %s c ORDER BY c.ancestor ASC, c.descendant ASC, c.depth ASC', self::TEST_CLOSURE_CLASS ) )
+			->getArrayResult();
+		
+		// Root self referencing row and descendants
+		$this->assertEquals( $rows[ 0 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 0 ][ 'descendant' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 0 ][ 'depth' ], 0 );
+		
+		$this->assertEquals( $rows[ 1 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 1 ][ 'descendant' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 1 ][ 'depth' ], 1 );
+		
+		$this->assertEquals( $rows[ 2 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 2 ][ 'descendant' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 2 ][ 'depth' ], 1 );
+		
+		$this->assertEquals( $rows[ 3 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 3 ][ 'descendant' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 3 ][ 'depth' ], 2 );
+		
+		$this->assertEquals( $rows[ 4 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 4 ][ 'descendant' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 4 ][ 'depth' ], 2 );
+		
+		// Sports self referencing row
+		$this->assertEquals( $rows[ 5 ][ 'ancestor' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 5 ][ 'descendant' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 5 ][ 'depth' ], 0 );
+		
+		// Fruits self referencing row
+		$this->assertEquals( $rows[ 6 ][ 'ancestor' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 6 ][ 'descendant' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 6 ][ 'depth' ], 0 );
+		
+		// Vegitables self referencing row and descendants
+		$this->assertEquals( $rows[ 7 ][ 'ancestor' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 7 ][ 'descendant' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 7 ][ 'depth' ], 0 );
+		
+		$this->assertEquals( $rows[ 8 ][ 'ancestor' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 8 ][ 'descendant' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 8 ][ 'depth' ], 1 );
+		
+		$this->assertEquals( $rows[ 9 ][ 'ancestor' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 9 ][ 'descendant' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 9 ][ 'depth' ], 1 );
+		
+		// Carrots self referencing row
+		$this->assertEquals( $rows[ 10 ][ 'ancestor' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 10 ][ 'descendant' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 10 ][ 'depth' ], 0 );
+		
+		// Potatoes self referencing row
+		$this->assertEquals( $rows[ 11 ][ 'ancestor' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 11 ][ 'descendant' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 11 ][ 'depth' ], 0 );
     }
+	
+	public function test_updateNodes_moveASubtreeAndVerifyTreeClosurePaths()
+    {
+		// We change a subtree's location
+		$vegitables = $this->em->getRepository( self::TEST_ENTITY_CLASS )
+			->findOneByTitle( 'Vegitables' );
+		$sports = $this->em->getRepository( self::TEST_ENTITY_CLASS )
+			->findOneByTitle( 'Sports' );
+		$vegitables->setParent( $sports );
+		
+		$this->em->persist( $vegitables );
+		$this->em->flush();
+		
+		// We then verify the closure paths
+		$repo 	= $this->em->getRepository(self::TEST_CLOSURE_CLASS);
+        $rows	= $this->em->createQuery( sprintf( 'SELECT c FROM %s c ORDER BY c.ancestor ASC, c.descendant ASC, c.depth ASC', self::TEST_CLOSURE_CLASS ) )
+			->getArrayResult();
+		
+		// Food self referencing row and descendants
+		$this->assertEquals( $rows[ 0 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 0 ][ 'descendant' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 0 ][ 'depth' ], 0 );
+		
+		$this->assertEquals( $rows[ 1 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 1 ][ 'descendant' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 1 ][ 'depth' ], 1 );
+		
+		// Sports self referencing row and descendants
+		$this->assertEquals( $rows[ 2 ][ 'ancestor' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 2 ][ 'descendant' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 2 ][ 'depth' ], 0 );
+		
+		$this->assertEquals( $rows[ 3 ][ 'ancestor' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 3 ][ 'descendant' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 3 ][ 'depth' ], 1 );
+		
+		$this->assertEquals( $rows[ 4 ][ 'ancestor' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 4 ][ 'descendant' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 4 ][ 'depth' ], 2 );
+		
+		$this->assertEquals( $rows[ 5 ][ 'ancestor' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 5 ][ 'descendant' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 5 ][ 'depth' ], 2 );
+		
+		// Fruits self referencing row
+		$this->assertEquals( $rows[ 6 ][ 'ancestor' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 6 ][ 'descendant' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 6 ][ 'depth' ], 0 );
+		
+		// Vegitables self referencing row and descendants
+		$this->assertEquals( $rows[ 7 ][ 'ancestor' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 7 ][ 'descendant' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 7 ][ 'depth' ], 0 );
+		
+		$this->assertEquals( $rows[ 8 ][ 'ancestor' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 8 ][ 'descendant' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 8 ][ 'depth' ], 1 );
+		
+		$this->assertEquals( $rows[ 9 ][ 'ancestor' ], $this->vegitables->getId() );
+		$this->assertEquals( $rows[ 9 ][ 'descendant' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 9 ][ 'depth' ], 1 );
+		
+		// Carrots self referencing row
+		$this->assertEquals( $rows[ 10 ][ 'ancestor' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 10 ][ 'descendant' ], $this->carrots->getId() );
+		$this->assertEquals( $rows[ 10 ][ 'depth' ], 0 );
+		
+		// Potatoes self referencing row
+		$this->assertEquals( $rows[ 11 ][ 'ancestor' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 11 ][ 'descendant' ], $this->potatoes->getId() );
+		$this->assertEquals( $rows[ 11 ][ 'depth' ], 0 );
+	}
+	
+	public function test_removeNode_removesClosurePathsOfNodeAndVerifyTree()
+	{
+		// We remove a subtree
+		$vegitables = $this->em->getRepository( self::TEST_ENTITY_CLASS )
+			->findOneByTitle( 'Vegitables' );
+		$this->em->remove( $vegitables );
+		$this->em->flush();
+		
+		// We then verify the closure paths
+		$repo 	= $this->em->getRepository(self::TEST_CLOSURE_CLASS);
+        $rows	= $this->em->createQuery( sprintf( 'SELECT c FROM %s c ORDER BY c.ancestor ASC, c.descendant ASC, c.depth ASC', self::TEST_CLOSURE_CLASS ) )
+			->getArrayResult();
+		
+		// Food self referencing row and descendants
+		$this->assertEquals( $rows[ 0 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 0 ][ 'descendant' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 0 ][ 'depth' ], 0 );
+		
+		$this->assertEquals( $rows[ 1 ][ 'ancestor' ], $this->food->getId() );
+		$this->assertEquals( $rows[ 1 ][ 'descendant' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 1 ][ 'depth' ], 1 );
+		
+		// Sports self referencing row
+		$this->assertEquals( $rows[ 2 ][ 'ancestor' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 2 ][ 'descendant' ], $this->sports->getId() );
+		$this->assertEquals( $rows[ 2 ][ 'depth' ], 0 );
+		
+		// Fruits self referencing row
+		$this->assertEquals( $rows[ 3 ][ 'ancestor' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 3 ][ 'descendant' ], $this->fruits->getId() );
+		$this->assertEquals( $rows[ 3 ][ 'depth' ], 0 );
+	}
     
     private function populate()
     {
         $root = new Category();
         $root->setTitle("Food");
+		$this->food = $root;
         
         $root2 = new Category();
         $root2->setTitle("Sports");
+		$this->sports = $root2;
         
         $child = new Category();
         $child->setTitle("Fruits");
         $child->setParent($root);
+		$this->fruits = $child;
         
         $child2 = new Category();
         $child2->setTitle("Vegitables");
         $child2->setParent($root);
+		$this->vegitables = $child2;
         
         $childsChild = new Category();
         $childsChild->setTitle("Carrots");
         $childsChild->setParent($child2);
+		$this->carrots = $childsChild;
         
         $potatoes = new Category();
         $potatoes->setTitle("Potatoes");
         $potatoes->setParent($child2);
+		$this->potatoes = $potatoes;
         
-        $this->em->persist($root);
-        $this->em->persist($root2);
-        $this->em->persist($child);
-        $this->em->persist($child2);
-        $this->em->persist($childsChild);
-        $this->em->persist($potatoes);
+        $this->em->persist($this->food);
+        $this->em->persist($this->sports);
+        $this->em->persist($this->fruits);
+        $this->em->persist($this->vegitables);
+        $this->em->persist($this->carrots);
+        $this->em->persist($this->potatoes);
+		
         $this->em->flush();
-        $this->em->clear();
-        
-        
+		$this->em->clear();
     }
 }

+ 3 - 2
tests/Gedmo/Tree/Fixture/Closure/Category.php

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

+ 17 - 0
tests/Gedmo/Tree/Proxy/TreeFixtureBehavioralCategoryProxy.php

@@ -68,4 +68,21 @@ class TreeFixtureBehavioralCategoryProxy extends \Tree\Fixture\BehavioralCategor
     {
         return array('__isInitialized__', 'id', 'title', 'lft', 'rgt', 'parent', 'children', 'slug');
     }
+
+    public function __clone()
+    {
+        if (!$this->__isInitialized__ && $this->_entityPersister) {
+            $this->__isInitialized__ = true;
+            $class = $this->_entityPersister->getClassMetadata();
+            $original = $this->_entityPersister->load($this->_identifier);
+            if ($original === null) {
+                throw new \Doctrine\ORM\EntityNotFoundException();
+            }
+            foreach ($class->reflFields AS $field => $reflProperty) {
+                $reflProperty->setValue($this, $reflProperty->getValue($original));
+            }
+            unset($this->_entityPersister, $this->_identifier);
+        }
+        
+    }
 }

+ 17 - 0
tests/Gedmo/Tree/Proxy/TreeFixtureCategoryProxy.php

@@ -62,4 +62,21 @@ class TreeFixtureCategoryProxy extends \Tree\Fixture\Category implements \Doctri
     {
         return array('__isInitialized__', 'id', 'title', 'lft', 'rgt', 'parentId', 'level', 'children', 'comments');
     }
+
+    public function __clone()
+    {
+        if (!$this->__isInitialized__ && $this->_entityPersister) {
+            $this->__isInitialized__ = true;
+            $class = $this->_entityPersister->getClassMetadata();
+            $original = $this->_entityPersister->load($this->_identifier);
+            if ($original === null) {
+                throw new \Doctrine\ORM\EntityNotFoundException();
+            }
+            foreach ($class->reflFields AS $field => $reflProperty) {
+                $reflProperty->setValue($this, $reflProperty->getValue($original));
+            }
+            unset($this->_entityPersister, $this->_identifier);
+        }
+        
+    }
 }

+ 0 - 23
tests/phpunit.dist.xml

@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<phpunit bootstrap="./bootstrap.php">
-    <testsuites>
-        <testsuite name="Translatable Extension">
-            <directory suffix=".php">./Gedmo/Translatable/</directory>
-        </testsuite>
-        <testsuite name="Sluggable Extension">
-            <directory suffix=".php">./Gedmo/Sluggable/</directory>
-        </testsuite>
-        <testsuite name="Tree Extension">
-            <directory suffix=".php">./Gedmo/Tree/</directory>
-        </testsuite>
-        <testsuite name="Timestampable Extension">
-            <directory suffix=".php">./Gedmo/Timestampable/</directory>
-        </testsuite>
-        <testsuite name="Mapping Extension">
-            <directory suffix=".php">./Gedmo/Mapping/</directory>
-        </testsuite>
-        <testsuite name="Loggable Extension">
-            <directory suffix=".php">./Gedmo/Loggable/</directory>
-        </testsuite>
-    </testsuites>
-</phpunit>