Przeglądaj źródła

Implemented a way to have many slug fields in Sluggable behavior

Dinoweb 14 lat temu
rodzic
commit
8a23176dd0

+ 2 - 0
lib/Gedmo/Mapping/Annotation/Sluggable.php

@@ -18,5 +18,7 @@ use Doctrine\Common\Annotations\Annotation;
 final class Sluggable extends Annotation
 {
     public $position = 0;
+    public $slugField = 'slug';
+
 }
 

+ 13 - 9
lib/Gedmo/Sluggable/Mapping/Driver/Annotation.php

@@ -94,7 +94,10 @@ class Annotation implements AnnotationDriverInterface
                 if (!$this->isValidField($meta, $field)) {
                     throw new InvalidMappingException("Cannot slug field - [{$field}] type is not valid and must be 'string' in class - {$meta->name}");
                 }
-                $config['fields'][] = array('field' => $field, 'position' => $sluggable->position);
+                if (!is_null($sluggable->slugField) and !$meta->hasField($sluggable->slugField)) {
+                    throw new InvalidMappingException("Unable to find slug [{$field}] as mapped property in entity - {$meta->name}");
+                }
+                $config['fields'][$sluggable->slugField][] = array('field' => $field, 'position' => $sluggable->position, 'slugField' => $sluggable->slugField);
             }
             // slug property
             if ($slug = $this->reader->getPropertyAnnotation($property, self::SLUG)) {
@@ -105,15 +108,16 @@ 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}");
                 }
-                if (isset($config['slug'])) {
-                    throw new InvalidMappingException("There cannot be two slug fields: [{$slugField}] and [{$config['slug']}], in class - {$meta->name}.");
-                }
+                
+                
+                $config['slugFields'][$field]['slug'] = $field;
+                $config['slugFields'][$field]['style'] = $slug->style;
+                $config['slugFields'][$field]['updatable'] = $slug->updatable;
+                $config['slugFields'][$field]['unique'] = $slug->unique;
+                $config['slugFields'][$field]['separator'] = $slug->separator;
 
-                $config['slug'] = $field;
-                $config['style'] = $slug->style;
-                $config['updatable'] = $slug->updatable;
-                $config['unique'] = $slug->unique;
-                $config['separator'] = $slug->separator;
+                
+                
             }
         }
     }

+ 14 - 7
lib/Gedmo/Sluggable/Mapping/Driver/Xml.php

@@ -65,11 +65,16 @@ class Xml extends BaseXml
                     if (!$this->isValidField($meta, $field)) {
                         throw new InvalidMappingException("Cannot slug field - [{$field}] type is not valid and must be 'string' in class - {$meta->name}");
                     }
-                    $options = array('position' => false, 'field' => $field);
+                    $options = array('position' => false, 'field' => $field, 'slugField'=>'slug');
                     if ($this->_isAttributeSet($mapping->sluggable, 'position')) {
                         $options['position'] =  (int)$this->_getAttribute($mapping->sluggable, 'position');
                     }
-                    $config['fields'][] = $options;
+                    
+                    if ($this->_isAttributeSet($mapping->sluggable, 'slugField')) {
+                        $options['slugField'] =  $this->_getAttribute($mapping->sluggable, 'slugField');
+                    }
+                    
+                    $config['fields'][$options['slugField']][] = $options;
                 } elseif (isset($mapping->slug)) {
                     /**
                      * @var \SimpleXmlElement $slug
@@ -81,17 +86,19 @@ class Xml extends BaseXml
                     if (isset($config['slug'])) {
                         throw new InvalidMappingException("There cannot be two slug fields: [{$slug}] and [{$config['slug']}], in class - {$meta->name}.");
                     }
-                    $config['slug'] = $field;
-                    $config['style'] = $this->_isAttributeSet($slug, 'style') ?
+                    
+                    
+                    $config['slugFields'][$field]['slug'] = $field;
+                    $config['slugFields'][$field]['style'] = $this->_isAttributeSet($slug, 'style') ?
                         $this->_getAttribute($slug, 'style') : 'default';
 
-                    $config['updatable'] = $this->_isAttributeSet($slug, 'updatable') ?
+                    $config['slugFields'][$field]['updatable'] = $this->_isAttributeSet($slug, 'updatable') ?
                         (bool)$this->_getAttribute($slug, 'updatable') : true;
 
-                    $config['unique'] = $this->_isAttributeSet($slug, 'unique') ?
+                    $config['slugFields'][$field]['unique'] = $this->_isAttributeSet($slug, 'unique') ?
                         (bool)$this->_getAttribute($slug, 'unique') : true;
 
-                    $config['separator'] = $this->_isAttributeSet($slug, 'separator') ?
+                    $config['slugFields'][$field]['separator'] = $this->_isAttributeSet($slug, 'separator') ?
                         $this->_getAttribute($slug, 'separator') : '-';
                 }
             }

+ 11 - 6
lib/Gedmo/Sluggable/Mapping/Driver/Yaml.php

@@ -63,7 +63,11 @@ class Yaml extends File implements Driver
                             throw new InvalidMappingException("Cannot slug field - [{$field}] type is not valid and must be 'string' in class - {$meta->name}");
                         }
                         $sluggable = $fieldMapping['gedmo'][array_search('sluggable', $fieldMapping['gedmo'])];
-                        $config['fields'][] = array('field' => $field, 'position' => $sluggable['position']);
+                            
+                        $slugField = isset($sluggable['slugField'])? $sluggable['slugField']:'slug';
+                        
+                        
+                        $config['fields'][$slugField][] = array('field' => $field, 'position' => $sluggable['position'], 'slugField' => $slugField);
                     } elseif (isset($fieldMapping['gedmo']['slug']) || in_array('slug', $fieldMapping['gedmo'])) {
                         $slug = $fieldMapping['gedmo']['slug'];
                         if (!$this->isValidField($meta, $field)) {
@@ -72,18 +76,19 @@ class Yaml extends File implements Driver
                         if (isset($config['slug'])) {
                             throw new InvalidMappingException("There cannot be two slug fields: [{$slugField}] and [{$config['slug']}], in class - {$meta->name}.");
                         }
+                        
 
-                        $config['slug'] = $field;
-                        $config['style'] = isset($slug['style']) ?
+                        $config['slugFields'][$field]['slug'] = $field;
+                        $config['slugFields'][$field]['style'] = isset($slug['style']) ?
                             (string)$slug['style'] : 'default';
 
-                        $config['updatable'] = isset($slug['updatable']) ?
+                        $config['slugFields'][$field]['updatable'] = isset($slug['updatable']) ?
                             (bool)$slug['updatable'] : true;
 
-                        $config['unique'] = isset($slug['unique']) ?
+                        $config['slugFields'][$field]['unique'] = isset($slug['unique']) ?
                             (bool)$slug['unique'] : true;
 
-                        $config['separator'] = isset($slug['separator']) ?
+                        $config['slugFields'][$field]['separator'] = isset($slug['separator']) ?
                             (string)$slug['separator'] : '-';
                     }
                 }

+ 90 - 67
lib/Gedmo/Sluggable/SluggableListener.php

@@ -116,8 +116,13 @@ 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);
-                $slug = $meta->getReflectionProperty($config['slug'])->getValue($object);
-                $this->persistedSlugs[$config['useObjectClass']][] = $slug;
+                
+                foreach ($config['fields'] as $slugField=>$fieldsForSlugField) {
+                    $slug = $meta->getReflectionProperty($slugField)->getValue($object);
+                    $this->persistedSlugs[$config['useObjectClass']][] = $slug;
+                }
+                
+                
             }
         }
         // we use onFlush and not preUpdate event to let other
@@ -125,9 +130,14 @@ class SluggableListener extends MappedEventSubscriber
         foreach ($ea->getScheduledObjectUpdates($uow) as $object) {
             $meta = $om->getClassMetadata(get_class($object));
             if ($config = $this->getConfiguration($om, $meta->name)) {
-                if ($config['updatable']) {
-                    $this->generateSlug($ea, $object);
+                foreach ($config['slugFields'] as $slugField) {
+                
+                    if ($slugField['updatable']) {
+                        $this->generateSlug($ea, $object);
+                    }
+                
                 }
+                
             }
         }
     }
@@ -156,70 +166,77 @@ class SluggableListener extends MappedEventSubscriber
         $uow = $om->getUnitOfWork();
         $changeSet = $ea->getObjectChangeSet($uow, $object);
         $config = $this->getConfiguration($om, $meta->name);
+        
+        foreach ($config['fields'] as $slugField=>$fieldsForSlugField) {
 
-        // sort sluggable fields by position
-        $fields = $config['fields'];
-        usort($fields, function($a, $b) {
-            if ($a['position'] == $b['position']) {
-                return 1;
-            }
-            return ($a['position'] < $b['position']) ? -1 : 1;
-        });
-
-        // collect the slug from fields
-        $slug = '';
-        $needToChangeSlug = false;
-        foreach ($fields as $sluggableField) {
-            if (isset($changeSet[$sluggableField['field']])) {
-                $needToChangeSlug = true;
+            // sort sluggable fields by position
+            $fields = $fieldsForSlugField;
+            usort($fields, function($a, $b) {
+                if ($a['position'] == $b['position']) {
+                    return 1;
+                }
+                return ($a['position'] < $b['position']) ? -1 : 1;
+            });
+    
+            // collect the slug from fields
+            $slug = '';
+            $needToChangeSlug = false;
+            foreach ($fields as $sluggableField) {
+                if (isset($changeSet[$sluggableField['field']])) {
+                    $needToChangeSlug = true;
+                }
+                $slug .= $meta->getReflectionProperty($sluggableField['field'])->getValue($object) . ' ';
             }
-            $slug .= $meta->getReflectionProperty($sluggableField['field'])->getValue($object) . ' ';
-        }
-        // if slug is not changed, no need further processing
-        if (!$needToChangeSlug) {
-            return; // nothing to do
-        }
-
-        if (!strlen(trim($slug))) {
-            throw new \Gedmo\Exception\UnexpectedValueException('Unable to find any non empty sluggable fields, make sure they have something at least.');
-        }
-
-        // build the slug
-        $slug = call_user_func_array(
-            $this->transliterator,
-            array($slug, $config['separator'], $object)
-        );
-
-        // stylize the slug
-        switch ($config['style']) {
-            case 'camel':
-                $slug = preg_replace_callback(
-                    '@^[a-z]|' . $config['separator'] . '[a-z]@smi',
-                    create_function('$m', 'return strtoupper($m[0]);'),
-                    $slug
+            // if slug is changed, do further processing
+            if ($needToChangeSlug) {            
+    
+                if (!strlen(trim($slug))) {
+                    throw new \Gedmo\Exception\UnexpectedValueException("Unable to find any non empty sluggable fields for slug [{$slugField}] , make sure they have something at least.");
+                }
+                
+                $slugFieldConfig = $config['slugFields'][$slugField];
+        
+                // build the slug
+                $slug = call_user_func_array(
+                    $this->transliterator,
+                    array($slug, $slugFieldConfig['separator'], $object)
                 );
-                break;
-
-            default:
-                // leave it as is
-                break;
-        }
-
-        // cut slug if exceeded in length
-        $mapping = $meta->getFieldMapping($config['slug']);
-        if (isset($mapping['length']) && strlen($slug) > $mapping['length']) {
-            $slug = substr($slug, 0, $mapping['length']);
-        }
-
-        // make unique slug if requested
-        if ($config['unique']) {
-            $this->exponent = 0;
-            $slug = $this->makeUniqueSlug($ea, $object, $slug);
+        
+                // stylize the slug
+                switch ($slugFieldConfig['style']) {
+                    case 'camel':
+                        $slug = preg_replace_callback(
+                            '@^[a-z]|' . $slugFieldConfig['separator'] . '[a-z]@smi',
+                            create_function('$m', 'return strtoupper($m[0]);'),
+                            $slug
+                        );
+                        break;
+        
+                    default:
+                        // leave it as is
+                        break;
+                }
+        
+                // cut slug if exceeded in length
+                $mapping = $meta->getFieldMapping($slugFieldConfig['slug']);
+                if (isset($mapping['length']) && strlen($slug) > $mapping['length']) {
+                    $slug = substr($slug, 0, $mapping['length']);
+                }
+        
+                // make unique slug if requested
+                if ($slugFieldConfig['unique']) {
+                    $this->exponent = 0;
+                    $arrayConfig = $slugFieldConfig;
+                    $arrayConfig['useObjectClass'] = $config['useObjectClass']; 
+                    $slug = $this->makeUniqueSlug($ea, $object, $slug, false, $arrayConfig);
+                }
+                // set the final slug
+                $meta->getReflectionProperty($slugFieldConfig['slug'])->setValue($object, $slug);
+                // recompute changeset
+                $ea->recomputeSingleObjectChangeSet($uow, $meta, $object);
+            
+            }
         }
-        // set the final slug
-        $meta->getReflectionProperty($config['slug'])->setValue($object, $slug);
-        // recompute changeset
-        $ea->recomputeSingleObjectChangeSet($uow, $meta, $object);
     }
 
     /**
@@ -230,11 +247,17 @@ class SluggableListener extends MappedEventSubscriber
      * @param string $preferedSlug
      * @return string - unique slug
      */
-    private function makeUniqueSlug(SluggableAdapter $ea, $object, $preferedSlug, $recursing = false)
+    private function makeUniqueSlug(SluggableAdapter $ea, $object, $preferedSlug, $recursing = false, $config = array())
     {
         $om = $ea->getObjectManager();
         $meta = $om->getClassMetadata(get_class($object));
-        $config = $this->getConfiguration($om, $meta->name);
+        if (count ($config) == 0)
+        {
+        
+            $config = $this->getConfiguration($om, $meta->name);
+        
+        }
+        
 
         // search for similar slug
         $result = $ea->getSimilarSlugs($object, $meta, $config, $preferedSlug);
@@ -265,7 +288,7 @@ class SluggableListener extends MappedEventSubscriber
                     $mapping['length'] - (strlen($i) + strlen($config['separator']))
                 );
                 $this->exponent = strlen($i) - 1;
-                $generatedSlug = $this->makeUniqueSlug($ea, $object, $generatedSlug, true);
+                $generatedSlug = $this->makeUniqueSlug($ea, $object, $generatedSlug, true, $config);
             }
             $preferedSlug = $generatedSlug;
         }

+ 126 - 0
tests/Gedmo/Sluggable/Fixture/TransArticleManySlug.php

@@ -0,0 +1,126 @@
+<?php
+
+namespace Sluggable\Fixture;
+
+use Gedmo\Sluggable\Sluggable;
+use Gedmo\Translatable\Translatable;
+use Gedmo\Mapping\Annotation as Gedmo;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ORM\Entity
+ */
+class TransArticleManySlug implements Sluggable, Translatable
+{
+    /**
+     * @ORM\Id
+     * @ORM\GeneratedValue
+     * @ORM\Column(type="integer")
+     */
+    private $id;
+
+    /**
+     * @Gedmo\Translatable
+     * @Gedmo\Sluggable(slugField="slug")
+     * @ORM\Column(type="string", length=64)
+     */
+    private $title;
+    
+    /**
+     * @Gedmo\Sluggable(slugField="uniqueSlug")
+     * @ORM\Column(type="string", length=64)
+     */
+    private $uniqueTitle;
+    
+    /**
+     * @Gedmo\Slug
+     * @ORM\Column(type="string", length=128)
+     */
+    private $uniqueSlug;
+
+    /**
+     * @Gedmo\Translatable
+     * @Gedmo\Sluggable(slugField="slug")
+     * @ORM\Column(type="string", length=16)
+     */
+    private $code;
+
+    /**
+     * @Gedmo\Translatable
+     * @Gedmo\Slug
+     * @ORM\Column(type="string", length=128)
+     */
+    private $slug;
+
+    /**
+     * @Gedmo\Locale
+     * Used locale to override Translation listener`s locale
+     */
+    private $locale;
+
+    public function addComment(Comment $comment)
+    {
+        $comment->setArticle($this);
+        $this->comments[] = $comment;
+    }
+
+    public function getComments()
+    {
+        return $this->comments;
+    }
+
+    public function setPage($page)
+    {
+        $this->page = $page;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+    
+    public function setUniqueTitle($uniqueTitle)
+    {
+        $this->uniqueTitle = $uniqueTitle;
+    }
+
+    public function getUniqueTitle()
+    {
+        return $this->uniqueTitle;
+    }
+
+    public function setCode($code)
+    {
+        $this->code = $code;
+    }
+
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    public function getSlug()
+    {
+        return $this->slug;
+    }
+    
+    public function getUniqueSlug()
+    {
+        return $this->uniqueSlug;
+    }
+
+    public function setTranslatableLocale($locale)
+    {
+        $this->locale = $locale;
+    }
+}

+ 103 - 0
tests/Gedmo/Sluggable/TranslatableManySlugTest.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace Gedmo\Sluggable;
+
+use Doctrine\Common\EventManager;
+use Tool\BaseTestCaseORM;
+use Doctrine\Common\Util\Debug,
+    Gedmo\Translatable\Translatable,
+    Gedmo\Translatable\Entity\Translation,
+    Gedmo\Translatable\TranslationListener,
+    Sluggable\Fixture\TransArticleManySlug;
+
+/**
+ * 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 TranslatableManySlugTest extends BaseTestCaseORM
+{
+    private $articleId;
+    private $translationListener;
+
+    const ARTICLE = 'Sluggable\\Fixture\\TransArticleManySlug';
+    const TRANSLATION = 'Gedmo\\Translatable\\Entity\\Translation';
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $evm = new EventManager;
+        $this->translationListener = new TranslationListener();
+        $this->translationListener->setTranslatableLocale('en_us');
+        $evm->addEventSubscriber(new SluggableListener);
+        $evm->addEventSubscriber($this->translationListener);
+
+        $this->getMockSqliteEntityManager($evm);
+        $this->populate();
+    }
+
+    public function testSlugAndTranslation()
+    {
+        $article = $this->em->find(self::ARTICLE, $this->articleId);
+        $this->assertTrue($article instanceof Translatable && $article instanceof Sluggable);
+        $this->assertEquals($article->getSlug(), 'the-title-my-code');
+        $this->assertEquals($article->getUniqueSlug(), 'the-unique-title');
+        $repo = $this->em->getRepository(self::TRANSLATION);
+
+        $translations = $repo->findTranslations($article);
+        $this->assertEquals(count($translations), 1);
+        $this->assertArrayHasKey('en_us', $translations);
+        $this->assertEquals(3, count($translations['en_us']));
+
+        $this->assertArrayHasKey('slug', $translations['en_us']);
+        $this->assertEquals('the-title-my-code', $translations['en_us']['slug']);
+
+        $article = $this->em->find(self::ARTICLE, $this->articleId);
+        $article->setTranslatableLocale('de_de');
+        $article->setCode('code in de');
+        $article->setTitle('title in de');
+
+        $this->em->persist($article);
+        $this->em->flush();
+        $this->em->clear();
+
+        $repo = $this->em->getRepository(self::TRANSLATION);
+        $translations = $repo->findTranslations($article);
+        $this->assertEquals(count($translations), 2);
+        $this->assertArrayHasKey('de_de', $translations);
+        $this->assertEquals(3, count($translations['de_de']));
+
+        $this->assertEquals('title in de', $translations['de_de']['title']);
+
+        $this->assertArrayHasKey('slug', $translations['de_de']);
+        $this->assertEquals('title-in-de-code-in-de', $translations['de_de']['slug']);
+    }
+
+
+
+    protected function getUsedEntityFixtures()
+    {
+        return array(
+            self::ARTICLE,
+            self::TRANSLATION
+        );
+    }
+
+    private function populate()
+    {
+        $article = new TransArticleManySlug();
+        $article->setTitle('the title');
+        $article->setCode('my code');
+        $article->setUniqueTitle('the unique title');
+        
+
+        $this->em->persist($article);
+        $this->em->flush();
+        $this->em->clear();
+        $this->articleId = $article->getId();
+    }
+}