Преглед на файлове

[Tree] Materialized Path: Preliminary support for ORM. Fixed some minor issues too.

comfortablynumb преди 13 години
родител
ревизия
4cd4af0e59

+ 4 - 1
lib/Gedmo/Mapping/Annotation/All.php

@@ -23,7 +23,10 @@ include __DIR__.'/Tree.php';
 include __DIR__.'/TreeClosure.php';
 include __DIR__.'/TreeLeft.php';
 include __DIR__.'/TreeLevel.php';
+include __DIR__.'/TreeLockTime.php';
 include __DIR__.'/TreeParent.php';
+include __DIR__.'/TreePath.php';
+include __DIR__.'/TreePathSource.php';
 include __DIR__.'/TreeRight.php';
 include __DIR__.'/TreeRoot.php';
-include __DIR__.'/Versioned.php';
+include __DIR__.'/Versioned.php';

+ 30 - 0
lib/Gedmo/Tree/Entity/Repository/MaterializedPathRepository.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Gedmo\Tree\Document\MongoDB\Repository;
+
+use Gedmo\Exception\InvalidArgumentException,
+    Gedmo\Tree\Strategy,
+    Gedmo\Tree\Strategy\ORM\MaterializedPath;
+
+/**
+ * The MaterializedPathRepository has some useful functions
+ * to interact with MaterializedPath tree. Repository uses
+ * the strategy used by listener
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Tree.Entity.Repository
+ * @subpackage MaterializedPathRepository
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class MaterializedPathRepository extends AbstractTreeRepository
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function validate()
+    {
+        return $this->listener->getStrategy($this->dm, $this->getClassMetadata()->name)->getName() === Strategy::MATERIALIZED_PATH;
+    }
+}

+ 2 - 0
lib/Gedmo/Tree/Mapping/Validator.php

@@ -2,6 +2,8 @@
 
 namespace Gedmo\Tree\Mapping;
 
+use Gedmo\Exception\InvalidMappingException;
+
 /**
  * This is a validator for all mapping drivers for Tree
  * behavioral extension, containing methods to validate

+ 9 - 1
lib/Gedmo/Tree/Strategy/AbstractMaterializedPath.php

@@ -259,7 +259,15 @@ abstract class AbstractMaterializedPath implements Strategy
 
         // If PathSource field is a string, we append the ID to the path
         if ($fieldMapping['type'] === 'string') {
-            $path .= '-'.$meta->getIdentifierValue($node);
+            if (method_exists($meta, 'getIdentifierValue')) {
+                $identifier = $meta->getIdentifierValue($node);
+            } else {
+                $identifierProp = $meta->getReflectionProperty($meta->getSingleIdentifierFieldName());
+                $identifierProp->setAccessible(true);
+                $identifier = $identifierProp->getValue($node);
+            }
+
+            $path .= '-'.$identifier;
         }
 
         $path .= $config['path_separator'];

+ 60 - 0
lib/Gedmo/Tree/Strategy/ORM/MaterializedPath.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Gedmo\Tree\Strategy\ORM;
+
+use Gedmo\Tree\Strategy\AbstractMaterializedPath;
+use Doctrine\Common\Persistence\ObjectManager;
+use Gedmo\Mapping\Event\AdapterInterface;
+
+/**
+ * This strategy makes tree using materialized path strategy
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Tree.Strategy.ODM.MongoDB
+ * @subpackage MaterializedPath
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class MaterializedPath extends AbstractMaterializedPath
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function removeNode($om, $meta, $config, $node)
+    {
+        $uow = $om->getUnitOfWork();
+        $pathProp = $meta->getReflectionProperty($config['path']);
+        $pathProp->setAccessible(true);
+        $path = $pathProp->getValue($node);
+
+        // Remove node's children
+        $qb = $om->createQueryBuilder();
+        $qb->select('e')
+            ->from($meta->name, 'e')
+            ->where($qb->expr()->like('e.'.$config['path'], $qb->expr()->literal($path.'%')));
+        $results = $qb->getQuery()
+            ->execute();
+        
+        foreach ($results as $node) {
+            $uow->scheduleForDelete($node);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getChildren($om, $meta, $config, $path)
+    {
+        $qb = $om->createQueryBuilder($meta->name);
+        $qb->select('e')
+            ->from($meta->name, 'e')
+            ->where($qb->expr()->like('e.'.$config['path'], $qb->expr()->literal($path.'%')))
+            ->andWhere('e.'.$config['path'].' != :path')
+            ->orderBy('e.'.$config['path'], 'asc');      // This may save some calls to updateNode
+        $qb->setParameter('path', $path);
+
+        return $qb->getQuery()
+            ->execute();
+    }
+}

+ 98 - 0
tests/Gedmo/Tree/Fixture/MPCategory.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace Tree\Fixture;
+
+use Gedmo\Tree\Node as NodeInterface;
+use Gedmo\Mapping\Annotation as Gedmo;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ORM\Entity
+ * @Gedmo\Tree(type="materializedPath")
+ */
+class MPCategory
+{
+    /**
+     * @ORM\Column(name="id", type="integer")
+     * @ORM\Id
+     * @ORM\GeneratedValue
+     */
+    private $id;
+
+    /**
+     * @Gedmo\TreePath
+     * @ORM\Column(name="path", type="string", length=3000, nullable=true)
+     */
+    private $path;
+
+    /**
+     * @Gedmo\TreePathSource
+     * @ORM\Column(name="title", type="string", length=64)
+     */
+    private $title;
+
+    /**
+     * @Gedmo\TreeParent
+     * @ORM\ManyToOne(targetEntity="MPCategory", inversedBy="children")
+     * @ORM\JoinColumns({
+     *   @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL")
+     * })
+     */
+    private $parentId;
+
+    /**
+     * @Gedmo\TreeLevel
+     * @ORM\Column(name="lvl", type="integer", nullable=true)
+     */
+    private $level;
+
+    /**
+     * @ORM\OneToMany(targetEntity="MPCategory", mappedBy="parent")
+     */
+    private $children;
+
+    /**
+     * @ORM\OneToMany(targetEntity="Article", mappedBy="category")
+     */
+    private $comments;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    public function setParent(MPCategory $parent = null)
+    {
+        $this->parentId = $parent;
+    }
+
+    public function getParent()
+    {
+        return $this->parentId;
+    }
+
+    public function setPath($path)
+    {
+        $this->path = $path;
+    }
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    public function getLevel()
+    {
+        return $this->level;
+    }
+}

+ 1 - 1
tests/Gedmo/Tree/MaterializedPathODMMongoDBTest.php

@@ -116,7 +116,7 @@ class MaterializedPathODMMongoDBTest extends BaseTestCaseMongoODM
         $this->setExpectedException('Gedmo\Exception\RuntimeException');
 
         $category = $this->createCategory();
-        $category->setTitle('1|');
+        $category->setTitle('1'.$this->config['path_separator']);
 
         $this->dm->persist($category);
         $this->dm->flush();

+ 146 - 0
tests/Gedmo/Tree/MaterializedPathORMTest.php

@@ -0,0 +1,146 @@
+<?php
+
+namespace Gedmo\Tree;
+
+use Doctrine\Common\EventManager;
+use Tool\BaseTestCaseORM;
+
+/**
+ * These are tests for Tree behavior
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @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 MaterializedPathORMTest extends BaseTestCaseORM
+{
+    const CATEGORY = "Tree\\Fixture\\MPCategory";
+
+    protected $config;
+    protected $listener;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->listener = new TreeListener;
+
+        $evm = new EventManager;
+        $evm->addEventSubscriber($this->listener);
+
+        $this->getMockSqliteEntityManager($evm);
+
+        $meta = $this->em->getClassMetadata(self::CATEGORY);
+        $this->config = $this->listener->getConfiguration($this->em, $meta->name);
+    }
+
+    /**
+     * @test
+     */
+    function insertUpdateAndRemove()
+    {
+        // Insert
+        $category = $this->createCategory();
+        $category->setTitle('1');
+        $category2 = $this->createCategory();
+        $category2->setTitle('2');
+        $category3 = $this->createCategory();
+        $category3->setTitle('3');
+        $category4 = $this->createCategory();
+        $category4->setTitle('4');
+
+        $category2->setParent($category);
+        $category3->setParent($category2);
+
+        $this->em->persist($category4);
+        $this->em->persist($category3);
+        $this->em->persist($category2);
+        $this->em->persist($category);
+        $this->em->flush();
+
+        $this->em->refresh($category);
+        $this->em->refresh($category2);
+        $this->em->refresh($category3);
+        $this->em->refresh($category4);
+
+        $this->assertEquals($this->generatePath(array('1' => $category->getId())), $category->getPath());
+        $this->assertEquals($this->generatePath(array('1' => $category->getId(), '2' => $category2->getId())), $category2->getPath());
+        $this->assertEquals($this->generatePath(array('1' => $category->getId(), '2' => $category2->getId(), '3' => $category3->getId())), $category3->getPath());
+        $this->assertEquals($this->generatePath(array('4' => $category4->getId())), $category4->getPath());
+        $this->assertEquals(1, $category->getLevel());
+        $this->assertEquals(2, $category2->getLevel());
+        $this->assertEquals(3, $category3->getLevel());
+        $this->assertEquals(1, $category4->getLevel());
+
+        // Update
+        $category2->setParent(null);
+
+        $this->em->persist($category2);
+        $this->em->flush();
+
+        $this->em->refresh($category);
+        $this->em->refresh($category2);
+        $this->em->refresh($category3);
+
+        $this->assertEquals($this->generatePath(array('1' => $category->getId())), $category->getPath());
+        $this->assertEquals($this->generatePath(array('2' => $category2->getId())), $category2->getPath());
+        $this->assertEquals($this->generatePath(array('2' => $category2->getId(), '3' => $category3->getId())), $category3->getPath());
+        $this->assertEquals(1, $category->getLevel());
+        $this->assertEquals(1, $category2->getLevel());
+        $this->assertEquals(2, $category3->getLevel());
+        $this->assertEquals(1, $category4->getLevel());
+
+        // Remove
+        $this->em->remove($category);
+        $this->em->remove($category2);
+        $this->em->flush();
+
+        $result = $this->em->createQueryBuilder()->select('c')->from(self::CATEGORY, 'c')->getQuery()->execute();
+        
+        $firstResult = $result[0];
+
+        $this->assertEquals(1, count($result));
+        $this->assertEquals('4', $firstResult->getTitle());
+        $this->assertEquals(1, $firstResult->getLevel());
+    }
+
+    /**
+     * @test
+     */
+    public function useOfSeparatorInPathSourceShouldThrowAnException()
+    {
+        $this->setExpectedException('Gedmo\Exception\RuntimeException');
+
+        $category = $this->createCategory();
+        $category->setTitle('1'.$this->config['path_separator']);
+
+        $this->em->persist($category);
+        $this->em->flush();
+    }
+
+    public function createCategory()
+    {
+        $class = self::CATEGORY;
+        return new $class;
+    }
+
+    protected function getUsedEntityFixtures()
+    {
+        return array(
+            self::CATEGORY
+        );
+    }
+
+    public function generatePath(array $sources)
+    {
+        $path = '';
+
+        foreach ($sources as $p => $id) {
+            $path .= $p.'-'.$id.$this->config['path_separator'];
+        }
+
+        return $path;
+    }
+}