Browse Source

issue #12 fix for JOINED table inheritance TREE extension updated to use rootEntityName for updates, will have to check other extensions for consistency

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

+ 15 - 17
lib/Gedmo/Tree/TreeListener.php

@@ -123,7 +123,7 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
             }
             $diff = $rightValue - $leftValue + 1;
             if ($diff > 2) {
-                $dql = "SELECT node FROM {$entityClass} node";
+                $dql = "SELECT node FROM {$meta->rootEntityName} node";
                 $dql .= " WHERE node.{$config['left']} BETWEEN :left AND :right";
                 $q = $em->createQuery($dql);
                 // get nodes for deletion
@@ -213,9 +213,8 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
      */
     private function _processPendingNode(EntityManager $em, $entity)
     {
-        $entityClass = get_class($entity);
-        $config = $this->getConfiguration($em, $entityClass);
-        $meta = $em->getClassMetadata($entityClass);
+        $meta = $em->getClassMetadata(get_class($entity));
+        $config = $this->getConfiguration($em, $meta->name);
         $parent = $meta->getReflectionProperty($config['parent'])->getValue($entity);
         $this->_adjustNodeWithParent($parent, $entity, $em);
     }
@@ -229,11 +228,10 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
      */
     private function _prepareRoot(EntityManager $em, $entity)
     {
-        $entityClass = get_class($entity);
-        $config = $this->getConfiguration($em, $entityClass);
+        $meta = $em->getClassMetadata(get_class($entity));
+        $config = $this->getConfiguration($em, $meta->name);
         
         $edge = $this->_treeEdge ?: $this->_getTreeEdge($em, $entity);
-        $meta = $em->getClassMetadata($entityClass);
         
         $meta->getReflectionProperty($config['left'])->setValue($entity, $edge + 1);
         $meta->getReflectionProperty($config['right'])->setValue($entity, $edge + 2);
@@ -251,10 +249,10 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
      */
     private function _adjustNodeWithParent($parent, $entity, EntityManager $em)
     {
-        $entityClass = get_class($entity);
-        $config = $this->getConfiguration($em, $entityClass);
+        $meta = $em->getClassMetadata(get_class($entity));
+        $config = $this->getConfiguration($em, $meta->name);
         $edge = $this->_getTreeEdge($em, $entity);
-        $meta = $em->getClassMetadata($entityClass);
+        
         
         $leftValue = $meta->getReflectionProperty($config['left'])->getValue($entity);
         $rightValue = $meta->getReflectionProperty($config['right'])->getValue($entity);
@@ -273,7 +271,7 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
                 $this->_sync($em, $entity, 2, '+', '>= ' . $parentRightValue);
                 // cannot schedule this update if other Nodes pending
                 $qb = $em->createQueryBuilder();
-                $qb->update($entityClass, 'node')
+                $qb->update($meta->rootEntityName, 'node')
                     ->set('node.' . $config['left'], $parentRightValue)
                     ->set('node.' . $config['right'], $parentRightValue + 1);
                 $entityIdentifiers = $meta->getIdentifierValues($entity);
@@ -318,14 +316,14 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
      */
     private function _sync(EntityManager $em, $entity, $shift, $dir, $conditions, $field = 'both')
     {
-        $entityClass = get_class($entity);
-        $config = $this->getConfiguration($em, $entityClass);
+        $meta = $em->getClassMetadata(get_class($entity));
+        $config = $this->getConfiguration($em, $meta->name);
         if ($field == 'both') {
             $this->_sync($em, $entity, $shift, $dir, $conditions, $config['left']);
             $field = $config['right'];
         }
         
-        $dql = "UPDATE {$entityClass} node";
+        $dql = "UPDATE {$meta->rootEntityName} node";
         $dql .= " SET node.{$field} = node.{$field} {$dir} {$shift}";
         $dql .= " WHERE node.{$field} {$conditions}";
         $q = $em->createQuery($dql);
@@ -341,10 +339,10 @@ class TreeListener extends MappedEventSubscriber implements EventSubscriber
      */
     private function _getTreeEdge(EntityManager $em, $entity)
     {
-        $entityClass = get_class($entity);
-        $config = $this->getConfiguration($em, $entityClass);
+        $meta = $em->getClassMetadata(get_class($entity));
+        $config = $this->getConfiguration($em, $meta->name);
         
-        $query = $em->createQuery("SELECT MAX(node.{$config['right']}) FROM {$entityClass} node");
+        $query = $em->createQuery("SELECT MAX(node.{$config['right']}) FROM {$meta->rootEntityName} node");
         $right = $query->getSingleScalarResult();
         return intval($right);
     }

+ 111 - 0
tests/Gedmo/Tree/Fixture/Role.php

@@ -0,0 +1,111 @@
+<?php
+namespace Tree\Fixture;
+
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ * @Entity(repositoryClass="Gedmo\Tree\Repository\TreeNodeRepository")
+ * @Table(name="`role`")
+ * @InheritanceType("JOINED")
+ * @DiscriminatorColumn(name="discr", type="string")
+ * @DiscriminatorMap({"user" = "User", "usergroup" = "UserGroup"})
+ */
+abstract class Role {
+
+  /**
+   * @Column(name="id", type="integer")
+   * @Id
+   * @GeneratedValue
+   * @var int
+   */
+  private $id;
+
+  /**
+   * @gedmo:TreeParent
+   * @ManyToOne(targetEntity="UserGroup", inversedBy="children")
+   * @var UserGroup
+   */
+  private $parent;
+
+  /**
+   * @OneToMany(targetEntity="Role", mappedBy="parent")
+   * @var Doctrine\Common\Collections\ArrayCollection
+   */
+  protected $children;
+
+  /**
+   * @gedmo:TreeLeft
+   * @Column(name="lft", type="integer")
+   */
+  private $lft;
+
+  /**
+   * @gedmo:TreeRight
+   * @Column(name="rgt", type="integer")
+   */
+  private $rgt;
+
+  /**
+   * @gedmo:TreeLevel
+   * @Column(name="lvl", type="integer")
+   */
+  private $lvl;
+
+  /**
+   * @Column(name="role", type="string", length=255, nullable=false)
+   * @var string
+   */
+  private $role;
+
+  public function __construct() {
+    $this->children      = new ArrayCollection();
+  }
+
+  /**
+   * @return UserGroup
+   */
+  public function getParent() {
+    return $this->parent;
+  }
+
+  /**
+   * @param UserGroup $parent
+   * @return Role
+   */
+  public function setParent(UserGroup $parent) {
+    $this->parent = $parent;
+    return $this;
+  }
+
+  public function getRoleId() {
+    return $this->role;
+  }
+
+  protected function setRoleId($roleId) {
+    $this->role = (string)$roleId;
+    return $this;
+  }
+
+  public function __toString() {
+    return $this->getRoleId();
+  }
+
+  public function getId() {
+    return $this->id;
+  }
+  
+  public function getLeft()
+  {
+      return $this->lft;
+  }
+  
+  public function getRight()
+  {
+      return $this->rgt;
+  }
+  
+  public function getLevel()
+  {
+      return $this->lvl;
+  }
+}

+ 124 - 0
tests/Gedmo/Tree/Fixture/User.php

@@ -0,0 +1,124 @@
+<?php
+namespace Tree\Fixture;
+
+/**
+ * @Entity(repositoryClass="Gedmo\Tree\Repository\TreeNodeRepository")
+ * @Table(name="`user`")
+ */
+class User extends Role {
+
+  const PASSWORD_SALT = 'dfJko$~346958rg!DFT]AEtzserf9giq)3/TAeg;aDFa43';
+
+  /**
+   * @Column(name="email", type="string", unique="true")
+   * @var string
+   */
+  private $email;
+
+  /**
+   * @Column(name="password_hash", type="string", length=32)
+   * @var string
+   */
+  private $passwordHash;
+
+  /**
+   * @Column(name="activation_code", type="string", length=12)
+   * @var string
+   */
+  private $activationCode;
+
+  /**
+   * @param string $email
+   * @param string $password
+   */
+  public function __construct($email, $password) {
+    parent::__construct();
+    $this
+      ->setEmail($email)
+      ->setPassword($password);
+  }
+
+  public function init() {
+    $this->setActivationCode($this->generateString(12));
+  }
+
+    /**
+   * Generates a random password
+   *
+   * @param int $length
+   * @return string
+   */
+  public function generateString($length = 8) {
+    $length = (int) $length;
+    if ($length < 0) {
+      throw new \Exception("Invalid password length '$length'");
+    }
+    $set = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    $num = strlen($set);
+    $ret = '';
+    for ($i = 0; $i < $length; $i++) {
+      $ret .= $set[rand(0, $num - 1)];
+    }
+    return $ret;
+  }
+
+  /**
+   * Generates a password hash
+   *
+   * @param string $password
+   * @return string
+   */
+  public function generatePasswordHash($password) {
+    return md5($password . self::PASSWORD_SALT);
+  }
+
+  /**
+   * @return string
+   */
+  public function getEmail() {
+    return $this->email;
+  }
+
+  /**
+   * @param string $email
+   * @return User
+   */
+  public function setEmail($email) {
+    $this->email = $email;
+    $this->setRoleId($email);
+    return $this;
+  }
+
+  /**
+   * @return string
+   */
+  public function getPasswordHash() {
+    return $this->passwordHash;
+  }
+
+  /**
+   * @param string $password
+   * @return User
+   */
+  public function setPassword($password) {
+    $this->passwordHash = $this->generatePasswordHash(trim($password));
+    return $this;
+  }
+
+  /**
+   * @return string
+   */
+  public function getActivationCode() {
+    return $this->activationCode;
+  }
+
+  /**
+   * @param string $activationCode
+   * @return User
+   */
+  public function setActivationCode($activationCode) {
+    $this->activationCode = $activationCode;
+    return $this;
+  }
+
+}

+ 39 - 0
tests/Gedmo/Tree/Fixture/UserGroup.php

@@ -0,0 +1,39 @@
+<?php
+namespace Tree\Fixture;
+
+/**
+ * Group entity
+ * 
+ * @Entity(repositoryClass="Gedmo\Tree\Repository\TreeNodeRepository")
+ * @Table(name="`user_group`")
+ */
+class UserGroup extends Role {
+
+  /**
+   * @Column(name="name", type="string", length=255)
+   * @var string
+   */
+  private $name;
+
+  public function __construct($name) {
+    $this->setName($name);
+  }
+
+  /**
+   * @return string
+   */
+  public function getRoleId() {
+    return $this->name;
+  }
+
+  public function getName() {
+    return $this->name;
+  }
+
+  public function setName($name) {
+    $this->name = $name;
+    $this->setRoleId($name);
+    return $this;
+  }
+
+}

+ 132 - 0
tests/Gedmo/Tree/MultiInheritanceTest2.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace Gedmo\Tree;
+
+use Doctrine\Common\Util\Debug;
+
+/**
+ * These are tests for Tree behavior
+ * Based on reported github issue #12 
+ * JOINED table inheritance mapping bug on Tree;
+ * 
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Tree
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class MultiInheritanceTest2 extends \PHPUnit_Framework_TestCase
+{
+    const TEST_ENTITY_USER_CLASS = "Tree\Fixture\User";
+    const TEST_ENTITY_USERGROUP_CLASS = "Tree\Fixture\UserGroup";
+    const TEST_ENTITY_ROLE_CLASS = "Tree\Fixture\Role";
+    
+    private $em;
+    
+    public function setUp()
+    {        
+        $config = new \Doctrine\ORM\Configuration();
+        $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
+        $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
+        $config->setProxyDir(__DIR__ . '/Proxy');
+        $config->setProxyNamespace('Gedmo\Tree\Proxies');
+        $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver());
+
+        $conn = array(
+            'driver' => 'pdo_sqlite',
+            'memory' => true,
+        );
+
+        //$config->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
+        
+        $evm = new \Doctrine\Common\EventManager();
+        $evm->addEventSubscriber(new \Gedmo\Tree\TreeListener());
+        $this->em = \Doctrine\ORM\EntityManager::create($conn, $config, $evm);
+
+        $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($this->em);
+        $schemaTool->dropSchema(array());
+        $schemaTool->createSchema(array(
+            $this->em->getClassMetadata(self::TEST_ENTITY_ROLE_CLASS),
+            $this->em->getClassMetadata(self::TEST_ENTITY_USER_CLASS),
+            $this->em->getClassMetadata(self::TEST_ENTITY_USERGROUP_CLASS)
+        ));
+        
+        $this->_populate();
+    }
+    
+    public function testConsistence()
+    {
+        $admins = $this->em->getRepository(self::TEST_ENTITY_USERGROUP_CLASS)->findOneByName('Admins');
+        $user3 = new \Tree\Fixture\User('user3@test.com', 'secret');
+        $user3->init();
+        $user3->setParent($admins);
+        
+        $this->em->persist($user3);
+        $this->em->flush();
+        $this->em->clear();
+        
+        // run tree consistence checks
+        
+        $everyBody = $this->em->getRepository(self::TEST_ENTITY_USERGROUP_CLASS)->findOneByName('Everybody');
+        $this->assertEquals(1, $everyBody->getLeft());
+        $this->assertEquals(14, $everyBody->getRight());
+        $this->assertEquals(0, $everyBody->getLevel());
+        
+        $admins = $this->em->getRepository(self::TEST_ENTITY_USERGROUP_CLASS)->findOneByName('Admins');
+        $this->assertEquals(2, $admins->getLeft());
+        $this->assertEquals(7, $admins->getRight());
+        $this->assertEquals(1, $admins->getLevel());
+        
+        $visitors = $this->em->getRepository(self::TEST_ENTITY_USERGROUP_CLASS)->findOneByName('Visitors');
+        $this->assertEquals(8, $visitors->getLeft());
+        $this->assertEquals(13, $visitors->getRight());
+        $this->assertEquals(1, $visitors->getLevel());
+        
+        $user0 = $this->em->getRepository(self::TEST_ENTITY_USER_CLASS)->findOneByEmail('user0@test.com');
+        $this->assertEquals(3, $user0->getLeft());
+        $this->assertEquals(4, $user0->getRight());
+        $this->assertEquals(2, $user0->getLevel());
+        
+        $user1 = $this->em->getRepository(self::TEST_ENTITY_USER_CLASS)->findOneByEmail('user1@test.com');
+        $this->assertEquals(9, $user1->getLeft());
+        $this->assertEquals(10, $user1->getRight());
+        $this->assertEquals(2, $user1->getLevel());
+        
+        $user2 = $this->em->getRepository(self::TEST_ENTITY_USER_CLASS)->findOneByEmail('user2@test.com');
+        $this->assertEquals(11, $user2->getLeft());
+        $this->assertEquals(12, $user2->getRight());
+        $this->assertEquals(2, $user2->getLevel());
+        
+        $user3 = $this->em->getRepository(self::TEST_ENTITY_USER_CLASS)->findOneByEmail('user3@test.com');
+        $this->assertEquals(5, $user3->getLeft());
+        $this->assertEquals(6, $user3->getRight());
+        $this->assertEquals(2, $user3->getLevel());
+    }
+    
+    private function _populate()
+    {
+        $everyBody = new \Tree\Fixture\UserGroup('Everybody');
+        $admins = new \Tree\Fixture\UserGroup('Admins');
+        $admins->setParent($everyBody);
+        $visitors = new \Tree\Fixture\UserGroup('Visitors');
+        $visitors->setParent($everyBody);
+        
+        $user0 = new \Tree\Fixture\User('user0@test.com', 'secret');
+        $user0->init();
+        $user0->setParent($admins);
+        $user1 = new \Tree\Fixture\User('user1@test.com', 'secret');
+        $user1->init();
+        $user1->setParent($visitors);
+        $user2 = new \Tree\Fixture\User('user2@test.com', 'secret');
+        $user2->init();
+        $user2->setParent($visitors);
+        
+        $this->em->persist($everyBody);
+        $this->em->persist($admins);
+        $this->em->persist($visitors);
+        $this->em->persist($user0);
+        $this->em->persist($user1);
+        $this->em->persist($user2);
+        $this->em->flush();
+        $this->em->clear();
+    }
+}

+ 95 - 0
tests/Gedmo/Tree/Proxy/TreeFixtureUserGroupProxy.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace Gedmo\Tree\Proxies;
+
+/**
+ * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
+ */
+class TreeFixtureUserGroupProxy extends \Tree\Fixture\UserGroup implements \Doctrine\ORM\Proxy\Proxy
+{
+    private $_entityPersister;
+    private $_identifier;
+    public $__isInitialized__ = false;
+    public function __construct($entityPersister, $identifier)
+    {
+        $this->_entityPersister = $entityPersister;
+        $this->_identifier = $identifier;
+    }
+    private function _load()
+    {
+        if (!$this->__isInitialized__ && $this->_entityPersister) {
+            $this->__isInitialized__ = true;
+            if ($this->_entityPersister->load($this->_identifier, $this) === null) {
+                throw new \Doctrine\ORM\EntityNotFoundException();
+            }
+            unset($this->_entityPersister, $this->_identifier);
+        }
+    }
+
+    
+    public function getRoleId()
+    {
+        $this->_load();
+        return parent::getRoleId();
+    }
+
+    public function getName()
+    {
+        $this->_load();
+        return parent::getName();
+    }
+
+    public function setName($name)
+    {
+        $this->_load();
+        return parent::setName($name);
+    }
+
+    public function getParent()
+    {
+        $this->_load();
+        return parent::getParent();
+    }
+
+    public function setParent(\Tree\Fixture\UserGroup $parent)
+    {
+        $this->_load();
+        return parent::setParent($parent);
+    }
+
+    public function __toString()
+    {
+        $this->_load();
+        return parent::__toString();
+    }
+
+    public function getId()
+    {
+        $this->_load();
+        return parent::getId();
+    }
+
+    public function getLeft()
+    {
+        $this->_load();
+        return parent::getLeft();
+    }
+
+    public function getRight()
+    {
+        $this->_load();
+        return parent::getRight();
+    }
+
+    public function getLevel()
+    {
+        $this->_load();
+        return parent::getLevel();
+    }
+
+
+    public function __sleep()
+    {
+        return array('__isInitialized__', 'id', 'parent', 'children', 'lft', 'rgt', 'lvl', 'role', 'name');
+    }
+}