Ver código fonte

[sluggable] initial slug handler implementation, and mapping

gediminasm 14 anos atrás
pai
commit
dbf25b9fa5

+ 1 - 0
lib/Gedmo/Mapping/Annotation/Slug.php

@@ -21,5 +21,6 @@ final class Slug extends Annotation
     public $style = 'default'; // or "camel"
     public $unique = true;
     public $separator = '-';
+    public $handlers = array();
 }
 

+ 35 - 0
lib/Gedmo/Mapping/Annotation/SlugHandler.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Gedmo\Mapping\Annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * SlugHandler annotation for Sluggable behavioral extension
+ *
+ * @Gedmo\Slug(handlers={
+ *      @Gedmo\SlugHandler(class="Some\Class", options={
+ *          @Gedmo\SlugHandlerOption(name="relation", value="parent"),
+ *          @Gedmo\SlugHandlerOption(name="separator", value="/")
+ *      }),
+ *      @Gedmo\SlugHandler(class="Some\Class", options={
+ *          @Gedmo\SlugHandlerOption(name="option", value="val"),
+ *          ...
+ *      }),
+ *      ...
+ * }, separator="-", updatable=false)
+ *
+ * @Annotation
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Mapping.Annotation
+ * @subpackage SlugHandler
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+final class SlugHandler extends Annotation
+{
+    public $class = '';
+    public $options = array();
+}
+

+ 35 - 0
lib/Gedmo/Mapping/Annotation/SlugHandlerOption.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Gedmo\Mapping\Annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * SlugHandlerOption annotation for Sluggable behavioral extension
+ *
+ * @Gedmo\Slug(handlers={
+ *      @Gedmo\SlugHandler(class="Some\Class", options={
+ *          @Gedmo\SlugHandlerOption(name="relation", value="parent"),
+ *          @Gedmo\SlugHandlerOption(name="separator", value="/")
+ *      }),
+ *      @Gedmo\SlugHandler(class="Some\Class", options={
+ *          @Gedmo\SlugHandlerOption(name="option", value="val"),
+ *          ...
+ *      }),
+ *      ...
+ * }, separator="-", updatable=false)
+ *
+ * @Annotation
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Mapping.Annotation
+ * @subpackage SlugHandlerOption
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+final class SlugHandlerOption extends Annotation
+{
+    public $name;
+    public $value;
+}
+

+ 97 - 0
lib/Gedmo/Sluggable/Handler/RelativeSlugHandler.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace Gedmo\Sluggable\Handler;
+
+use Doctrine\Common\Persistence\ObjectManager;
+use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Gedmo\Sluggable\SluggableListener;
+use Gedmo\Sluggable\Mapping\Event\SluggableAdapter;
+use Gedmo\Tool\Wrapper\AbstractWrapper;
+use Gedmo\Exception\InvalidMappingException;
+
+class RelativeSlugHandler implements SlugHandlerInterface
+{
+    /**
+     * @var Doctrine\Common\Persistence\ObjectManager
+     */
+    private $om;
+
+    /**
+     * @var Gedmo\Sluggable\SluggableListener
+     */
+    private $sluggable;
+
+    /**
+     * Options for relative slug handler
+     *
+     * @var array
+     */
+    private $options;
+
+    private $originalTransliterator;
+
+    private $parts = array();
+
+    /**
+     * {@inheritDoc}
+     */
+    public function __construct(ObjectManager $om, SluggableListener $sluggable, array $options)
+    {
+        $this->om = $om;
+        $this->sluggable = $sluggable;
+        $default = array(
+            'recursive' => true,
+            'separator' => '/'
+        );
+        $this->options = array_merge($default, $options);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function postSlugBuild(SluggableAdapter $ea, $field, $object, &$slug)
+    {
+        $this->originalTransliterator = $this->sluggable->getTransliterator();
+        $this->sluggable->setTransliterator(array($this, 'transliterate'));
+        $this->parts = array();
+        $wrapped = AbstractWrapper::wrapp($object, $this->om);
+        do {
+            $relation = $wrapped->getPropertyValue($this->options['relation']);
+            if ($relation) {
+                $wrappedRelation = AbstractWrapper::wrapp($relation, $this->om);
+                array_unshift($this->parts, $wrappedRelation->getPropertyValue($this->options['targetField']));
+                $wrapped = $wrappedRelation;
+            }
+        } while ($this->options['recursive'] && $relation);
+        //var_dump($slug);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public static function validate(array $options, ClassMetadata $meta)
+    {
+        if (!$meta->isSingleValuedAssociation($options['relation'])) {
+            throw new InvalidMappingException("Unable to find slug relation through field - [{$options['relation']}] in class - {$meta->name}");
+        }
+        /*if (!$meta->isSingleValuedAssociation($options['relation'])) {
+            throw new InvalidMappingException("Unable to find slug relation through field - [{$options['relation']}] in class - {$meta->name}");
+        }*/
+    }
+
+    public function transliterate($text, $separator, $object)
+    {
+        foreach ($this->parts as &$part) {
+            $part = call_user_func_array(
+                $this->originalTransliterator,
+                array($part, $separator, $object)
+            );
+        }
+        $this->parts[] = call_user_func_array(
+            $this->originalTransliterator,
+            array($text, $separator, $object)
+        );
+        $this->sluggable->setTransliterator($this->originalTransliterator);
+        return implode($this->options['separator'], $this->parts);
+    }
+}

+ 38 - 0
lib/Gedmo/Sluggable/Handler/SlugHandlerInterface.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Gedmo\Sluggable\Handler;
+
+use Doctrine\Common\Persistence\ObjectManager;
+use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Gedmo\Sluggable\SluggableListener;
+use Gedmo\Sluggable\Mapping\Event\SluggableAdapter;
+
+interface SlugHandlerInterface
+{
+    /**
+     * Construct the slug handler
+     *
+     * @param Doctrine\Common\Persistence\ObjectManager $om
+     * @param Gedmo\Sluggable\SluggableListener $sluggable
+     * @param array $options
+     */
+    function __construct(ObjectManager $om, SluggableListener $sluggable, array $options);
+
+    /**
+     * Callback on slug handlers right after the slug is built
+     *
+     * @param Gedmo\Sluggable\Mapping\Event\SluggableAdapter $ea
+     * @param string $field
+     * @param object $object
+     * @param string $slug
+     * @return void
+     */
+    function postSlugBuild(SluggableAdapter $ea, $field, $object, &$slug);
+
+    /**
+     * Validate handler options
+     *
+     * @param array $options
+     */
+    static function validate(array $options, ClassMetadata $meta);
+}

+ 35 - 0
lib/Gedmo/Sluggable/Mapping/Driver/Annotation.php

@@ -2,6 +2,8 @@
 
 namespace Gedmo\Sluggable\Mapping\Driver;
 
+use Gedmo\Mapping\Annotation\SlugHandler;
+use Gedmo\Mapping\Annotation\SlugHandlerOption;
 use Gedmo\Mapping\Driver\AnnotationDriverInterface,
     Doctrine\Common\Annotations\AnnotationReader,
     Doctrine\Common\Persistence\Mapping\ClassMetadata,
@@ -32,6 +34,16 @@ class Annotation implements AnnotationDriverInterface
      */
     const SLUG = 'Gedmo\\Mapping\\Annotation\\Slug';
 
+    /**
+     * SlugHandler extension annotation
+     */
+    const HANDLER = 'Gedmo\\Mapping\\Annotation\\SlugHandler';
+
+    /**
+     * SlugHandler option annotation
+     */
+    const HANDLER_OPTION ='Gedmo\\Mapping\\Annotation\\SlugHandlerOption';
+
     /**
      * List of types which are valid for slug and sluggable fields
      *
@@ -116,6 +128,29 @@ class Annotation implements AnnotationDriverInterface
                 if (!$this->isValidField($meta, $field)) {
                     throw new InvalidMappingException("Cannot use field - [{$field}] for slug storage, type is not valid and must be 'string' in class - {$meta->name}");
                 }
+                // process slug handlers
+                if (is_array($slug->handlers) && $slug->handlers) {
+                    foreach ($slug->handlers as $handler) {
+                        if (!$handler instanceof SlugHandler) {
+                            throw new InvalidMappingException("SlugHandler: {$handler} should be instance of SlugHandler annotation in entity - {$meta->name}");
+                        }
+                        if (!strlen($handler->class)) {
+                            throw new InvalidMappingException("SlugHandler class: {$handler->class} should be a valid class name in entity - {$meta->name}");
+                        }
+                        $class = $handler->class;
+                        $config['handlers'][$class] = array();
+                        foreach ((array)$handler->options as $option) {
+                            if (!$option instanceof SlugHandlerOption) {
+                                throw new InvalidMappingException("SlugHandlerOption: {$option} should be instance of SlugHandlerOption annotation in entity - {$meta->name}");
+                            }
+                            if (!strlen($option->name)) {
+                                throw new InvalidMappingException("SlugHandlerOption name: {$option->name} should be valid name in entity - {$meta->name}");
+                            }
+                            $config['handlers'][$class][$option->name] = $option->value;
+                        }
+                        $class::validate($config['handlers'][$class], $meta);
+                    }
+                }
 
                 $config['slugFields'][$field]['slug'] = $field;
                 $config['slugFields'][$field]['style'] = $slug->style;

+ 29 - 4
lib/Gedmo/Sluggable/SluggableListener.php

@@ -5,6 +5,7 @@ namespace Gedmo\Sluggable;
 use Doctrine\Common\EventArgs;
 use Gedmo\Mapping\MappedEventSubscriber;
 use Gedmo\Sluggable\Mapping\Event\SluggableAdapter;
+use Doctrine\Common\Persistence\ObjectManager;
 
 /**
  * The SluggableListener handles the generation of slugs
@@ -46,6 +47,13 @@ class SluggableListener extends MappedEventSubscriber
      */
     private $persistedSlugs = array();
 
+    /**
+     * List of initialized slug handlers
+     *
+     * @var array
+     */
+    private $handlers = array();
+
     /**
      * Specifies the list of events to listen
      *
@@ -116,7 +124,7 @@ class SluggableListener extends MappedEventSubscriber
             if ($config = $this->getConfiguration($om, $meta->name)) {
                 // generate first to exclude this object from similar persisted slugs result
                 $this->generateSlug($ea, $object);
-                foreach ($config['fields'] as $slugField=>$fieldsForSlugField) {
+                foreach ($config['fields'] as $slugField => $fieldsForSlugField) {
                     $slug = $meta->getReflectionProperty($slugField)->getValue($object);
                     $this->persistedSlugs[$config['useObjectClass']][$slugField][] = $slug;
                 }
@@ -144,6 +152,14 @@ class SluggableListener extends MappedEventSubscriber
         return __NAMESPACE__;
     }
 
+    private function getHandler($class, ObjectManager $om, array $options)
+    {
+        if (!isset($this->handlers[$class])) {
+            $this->handlers[$class] = new $class($om, $this, $options);
+        }
+        return $this->handlers[$class];
+    }
+
     /**
      * Creates the slug for object being flushed
      *
@@ -160,8 +176,7 @@ class SluggableListener extends MappedEventSubscriber
         $uow = $om->getUnitOfWork();
         $changeSet = $ea->getObjectChangeSet($uow, $object);
         $config = $this->getConfiguration($om, $meta->name);
-        foreach ($config['fields'] as $slugField=>$fieldsForSlugField) {
-
+        foreach ($config['fields'] as $slugField => $fieldsForSlugField) {
             // sort sluggable fields by position
             $fields = $fieldsForSlugField;
             usort($fields, function($a, $b) {
@@ -186,6 +201,16 @@ class SluggableListener extends MappedEventSubscriber
                     throw new \Gedmo\Exception\UnexpectedValueException("Unable to find any non empty sluggable fields for slug [{$slugField}] , make sure they have something at least.");
                 }
 
+                // notify slug handlers --> postSlugBuild
+                if (isset($config['handlers'])) {
+                    foreach ($config['handlers'] as $class => $options) {
+                        $this
+                            ->getHandler($class, $om, $options)
+                            ->postSlugBuild($ea, $slugField, $object, $slug)
+                        ;
+                    }
+                }
+
                 $slugFieldConfig = $config['slugFields'][$slugField];
                 // build the slug
                 $slug = call_user_func_array(
@@ -297,7 +322,7 @@ class SluggableListener extends MappedEventSubscriber
         $result = array();
         if (isset($this->persistedSlugs[$class][$slugField])) {
             array_walk($this->persistedSlugs[$class][$slugField], function($val) use ($preferedSlug, &$result, $slugField) {
-                if (preg_match("/^{$preferedSlug}.*/smi", $val)) {
+                if (preg_match("@^{$preferedSlug}.*@smi", $val)) {
                     $result[] = array($slugField => $val);
                 }
             });

+ 76 - 0
tests/Gedmo/Mapping/Fixture/Sluggable.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace Mapping\Fixture;
+
+use Gedmo\Mapping\Annotation as Gedmo;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ORM\Entity
+ */
+class Sluggable
+{
+    /**
+     * @ORM\Id
+     * @ORM\GeneratedValue
+     * @ORM\Column(type="integer")
+     */
+    private $id;
+
+    /**
+     * @Gedmo\Sluggable
+     * @ORM\Column(name="title", type="string", length=64)
+     */
+    private $title;
+
+    /**
+     * @Gedmo\Sluggable
+     * @ORM\Column(name="code", type="string", length=16)
+     */
+    private $code;
+
+    /**
+     * @Gedmo\Slug(handlers={
+     *      @Gedmo\SlugHandler(class="Some\Class", options={
+     *          @Gedmo\SlugHandlerOption(name="relation", value="parent"),
+     *          @Gedmo\SlugHandlerOption(name="separator", value="/")
+     *      }),
+     *      @Gedmo\SlugHandler(class="Some\Class2", options={
+     *          @Gedmo\SlugHandlerOption(name="option", value="val"),
+     *          @Gedmo\SlugHandlerOption(name="option2", value="val2")
+     *      })
+     * }, separator="-", updatable=false)
+     * @ORM\Column(name="slug", type="string", length=64, unique=true)
+     */
+    private $slug;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    public function setCode($code)
+    {
+        $this->code = $code;
+    }
+
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    public function getSlug()
+    {
+        return $this->slug;
+    }
+}

+ 40 - 9
tests/Gedmo/Mapping/SluggableMappingTest.php

@@ -19,6 +19,7 @@ use Doctrine\Common\Util\Debug,
 class SluggableMappingTest extends \PHPUnit_Framework_TestCase
 {
     const TEST_YAML_ENTITY_CLASS = 'Mapping\Fixture\Yaml\Category';
+    const SLUGGABLE = 'Mapping\Fixture\Sluggable';
     private $em;
 
     public function setUp()
@@ -33,6 +34,15 @@ class SluggableMappingTest extends \PHPUnit_Framework_TestCase
             new YamlDriver(array(__DIR__ . '/Driver/Yaml')),
             'Mapping\Fixture\Yaml'
         );
+        $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+        \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
+            'Gedmo\\Mapping\\Annotation',
+            VENDOR_PATH . '/../lib'
+        );
+        $chainDriverImpl->addDriver(
+            new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader),
+            'Mapping\Fixture'
+        );
         $config->setMetadataDriverImpl($chainDriverImpl);
 
         $conn = array(
@@ -40,17 +50,9 @@ class SluggableMappingTest extends \PHPUnit_Framework_TestCase
             'memory' => true,
         );
 
-        //$config->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
-
         $evm = new \Doctrine\Common\EventManager();
         $evm->addEventSubscriber(new SluggableListener());
         $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_YAML_ENTITY_CLASS)
-        ));
     }
 
     public function testYamlMapping()
@@ -76,6 +78,35 @@ class SluggableMappingTest extends \PHPUnit_Framework_TestCase
         $this->assertTrue($config['slugFields']['slug']['unique']);
         $this->assertArrayHasKey('updatable', $config['slugFields']['slug']);
         $this->assertTrue($config['slugFields']['slug']['updatable']);
-        
+    }
+
+    public function testSlugHandlerMapping()
+    {
+        $meta = $this->em->getClassMetadata(self::SLUGGABLE);
+        $cacheId = ExtensionMetadataFactory::getCacheId(
+            self::SLUGGABLE,
+            'Gedmo\Sluggable'
+        );
+        $config = $this->em->getMetadataFactory()->getCacheDriver()->fetch($cacheId);
+
+        $this->assertArrayHasKey('handlers', $config);
+        $handlers = $config['handlers'];
+        $this->assertEquals(2, count($handlers));
+        $this->assertArrayHasKey('Some\\Class', $handlers);
+        $this->assertArrayHasKey('Some\\Class2', $handlers);
+
+        $first = $handlers['Some\\Class'];
+        $this->assertEquals(2, count($first));
+        $this->assertArrayHasKey('relation', $first);
+        $this->assertArrayHasKey('separator', $first);
+        $this->assertEquals('parent', $first['relation']);
+        $this->assertEquals('/', $first['separator']);
+
+        $second = $handlers['Some\\Class2'];
+        $this->assertEquals(2, count($second));
+        $this->assertArrayHasKey('option', $second);
+        $this->assertArrayHasKey('option2', $second);
+        $this->assertEquals('val', $second['option']);
+        $this->assertEquals('val2', $second['option2']);
     }
 }

+ 7 - 13
tests/Gedmo/Mapping/TimestampableMappingTest.php

@@ -10,7 +10,7 @@ use Doctrine\Common\Util\Debug,
 
 /**
  * These are mapping tests for timestampable extension
- * 
+ *
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Mapping
  * @link http://www.gediminasm.org
@@ -22,7 +22,7 @@ class TimestampableMappingTest extends \PHPUnit_Framework_TestCase
     private $em;
 
     public function setUp()
-    {        
+    {
         $config = new \Doctrine\ORM\Configuration();
         $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
         $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
@@ -30,7 +30,7 @@ class TimestampableMappingTest extends \PHPUnit_Framework_TestCase
         $config->setProxyNamespace('Gedmo\Mapping\Proxy');
         $chainDriverImpl = new DriverChain;
         $chainDriverImpl->addDriver(
-            new YamlDriver(array(__DIR__ . '/Driver/Yaml')), 
+            new YamlDriver(array(__DIR__ . '/Driver/Yaml')),
             'Mapping\Fixture\Yaml'
         );
         $config->setMetadataDriverImpl($chainDriverImpl);
@@ -41,23 +41,17 @@ class TimestampableMappingTest extends \PHPUnit_Framework_TestCase
         );
 
         //$config->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
-        
+
         $evm = new \Doctrine\Common\EventManager();
         $evm->addEventSubscriber(new TimestampableListener());
         $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_YAML_ENTITY_CLASS)
-        ));
     }
-    
+
     public function testYamlMapping()
     {
         $meta = $this->em->getClassMetadata(self::TEST_YAML_ENTITY_CLASS);
         $cacheId = ExtensionMetadataFactory::getCacheId(
-            self::TEST_YAML_ENTITY_CLASS, 
+            self::TEST_YAML_ENTITY_CLASS,
             'Gedmo\Timestampable'
         );
         $config = $this->em->getMetadataFactory()->getCacheDriver()->fetch($cacheId);
@@ -67,7 +61,7 @@ class TimestampableMappingTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals('updated', $config['update'][0]);
         $this->assertArrayHasKey('change', $config);
         $onChange = $config['change'][0];
-        
+
         $this->assertEquals('changed', $onChange['field']);
         $this->assertEquals('title', $onChange['trackedField']);
         $this->assertEquals('Test', $onChange['value']);

+ 6 - 12
tests/Gedmo/Mapping/TranslatableMappingTest.php

@@ -10,7 +10,7 @@ use Doctrine\Common\Util\Debug,
 
 /**
  * These are mapping tests for translatable behavior
- * 
+ *
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Mapping
  * @link http://www.gediminasm.org
@@ -22,7 +22,7 @@ class TranslatableMappingTest extends \PHPUnit_Framework_TestCase
     private $em;
 
     public function setUp()
-    {        
+    {
         $config = new \Doctrine\ORM\Configuration();
         $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
         $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
@@ -30,7 +30,7 @@ class TranslatableMappingTest extends \PHPUnit_Framework_TestCase
         $config->setProxyNamespace('Gedmo\Mapping\Proxy');
         $chainDriverImpl = new DriverChain;
         $chainDriverImpl->addDriver(
-            new YamlDriver(array(__DIR__ . '/Driver/Yaml')), 
+            new YamlDriver(array(__DIR__ . '/Driver/Yaml')),
             'Mapping\Fixture\Yaml'
         );
         $config->setMetadataDriverImpl($chainDriverImpl);
@@ -41,25 +41,19 @@ class TranslatableMappingTest extends \PHPUnit_Framework_TestCase
         );
 
         //$config->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
-        
+
         $evm = new \Doctrine\Common\EventManager();
         $this->translatableListener = new TranslationListener();
         $this->translatableListener->setTranslatableLocale('en_us');
         $evm->addEventSubscriber($this->translatableListener);
         $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_YAML_ENTITY_CLASS)
-        ));
     }
-    
+
     public function testYamlMapping()
     {
         $meta = $this->em->getClassMetadata(self::TEST_YAML_ENTITY_CLASS);
         $cacheId = ExtensionMetadataFactory::getCacheId(
-            self::TEST_YAML_ENTITY_CLASS, 
+            self::TEST_YAML_ENTITY_CLASS,
             'Gedmo\Translatable'
         );
         $config = $this->em->getMetadataFactory()->getCacheDriver()->fetch($cacheId);

+ 124 - 0
tests/Gedmo/Sluggable/Fixture/RelativeSlug.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace Sluggable\Fixture;
+
+use Gedmo\Mapping\Annotation as Gedmo;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @Gedmo\Tree(type="nested")
+ * @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
+ */
+class RelativeSlug
+{
+    /**
+     * @ORM\Id
+     * @ORM\GeneratedValue
+     * @ORM\Column(type="integer")
+     */
+    private $id;
+
+    /**
+     * @Gedmo\Sluggable
+     * @ORM\Column(name="title", type="string", length=64)
+     */
+    private $title;
+
+    /**
+     * @Gedmo\Slug(handlers={
+     *      @Gedmo\SlugHandler(class="Gedmo\Sluggable\Handler\RelativeSlugHandler", options={
+     *          @Gedmo\SlugHandlerOption(name="relation", value="parent"),
+     *          @Gedmo\SlugHandlerOption(name="targetField", value="title"),
+     *          @Gedmo\SlugHandlerOption(name="separator", value="/")
+     *      })
+     * }, separator="-", updatable=false)
+     * @ORM\Column(name="slug", type="string", length=64, unique=true)
+     */
+    private $slug;
+
+    /**
+     * @Gedmo\TreeParent
+     * @ORM\ManyToOne(targetEntity="RelativeSlug")
+     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL")
+     */
+    private $parent;
+
+    /**
+     * @Gedmo\TreeLeft
+     * @ORM\Column(type="integer")
+     */
+    private $lft;
+
+    /**
+     * @Gedmo\TreeRight
+     * @ORM\Column(type="integer")
+     */
+    private $rgt;
+
+    /**
+     * @Gedmo\TreeRoot
+     * @ORM\Column(type="integer")
+     */
+    private $root;
+
+/**
+     * @Gedmo\TreeLevel
+     * @ORM\Column(name="lvl", type="integer")
+     */
+    private $level;
+
+    public function setParent($parent = null)
+    {
+        $this->parent = $parent;
+    }
+
+    public function getChildren()
+    {
+        return $this->children;
+    }
+
+    public function getParent()
+    {
+        return $this->parent;
+    }
+
+    public function getRoot()
+    {
+        return $this->root;
+    }
+
+    public function getLeft()
+    {
+        return $this->lft;
+    }
+
+    public function getRight()
+    {
+        return $this->rgt;
+    }
+
+    public function getLevel()
+    {
+        return $this->level;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+
+    public function getSlug()
+    {
+        return $this->slug;
+    }
+}

+ 89 - 0
tests/Gedmo/Sluggable/RelativeSlugHandlerTest.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace Gedmo\Sluggable;
+
+use Doctrine\Common\EventManager;
+use Tool\BaseTestCaseORM;
+use Sluggable\Fixture\RelativeSlug;
+use Gedmo\Tree\TreeListener;
+
+/**
+ * These are tests for Sluggable behavior
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Sluggable
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class RelativeSlugHandlerTest extends BaseTestCaseORM
+{
+    const TARGET = "Sluggable\\Fixture\\RelativeSlug";
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $evm = new EventManager;
+        $evm->addEventSubscriber(new TreeListener);
+        $evm->addEventSubscriber(new SluggableListener);
+
+        $conn = array(
+            'driver' => 'pdo_mysql',
+            'host' => '127.0.0.1',
+            'dbname' => 'test',
+            'user' => 'root',
+            'password' => 'nimda'
+        );
+        //$this->getMockCustomEntityManager($conn, $evm);
+        $this->getMockSqliteEntityManager($evm);
+    }
+
+    public function testSlugGeneration()
+    {
+        $this->populate();
+    }
+
+    protected function getUsedEntityFixtures()
+    {
+        return array(
+            self::TARGET
+        );
+    }
+
+    private function populate()
+    {
+        $repo = $this->em->getRepository(self::TARGET);
+
+        $food = new RelativeSlug;
+        $food->setTitle('Food');
+
+        $fruits = new RelativeSlug;
+        $fruits->setTitle('Fruits');
+
+        $vegitables = new RelativeSlug;
+        $vegitables->setTitle('Vegitables');
+
+        $milk = new RelativeSlug;
+        $milk->setTitle('Milk');
+
+        $meat = new RelativeSlug;
+        $meat->setTitle('Meat');
+
+        $oranges = new RelativeSlug;
+        $oranges->setTitle('Oranges');
+
+        $citrons = new RelativeSlug;
+        $citrons->setTitle('Citrons');
+
+        $repo
+            ->persistAsFirstChild($food)
+            ->persistAsFirstChildOf($fruits, $food)
+            ->persistAsFirstChildOf($vegitables, $food)
+            ->persistAsLastChildOf($milk, $food)
+            ->persistAsLastChildOf($meat, $food)
+            ->persistAsFirstChildOf($oranges, $fruits)
+            ->persistAsFirstChildOf($citrons, $fruits);
+
+        $this->em->flush();
+    }
+}