Jelajahi Sumber

[translatable] begin adaption of personal translation in listener

gedi 13 tahun lalu
induk
melakukan
6ac6ffcabd

+ 150 - 0
lib/Gedmo/Translatable/Entity/AbstractPersonalTranslation.php

@@ -0,0 +1,150 @@
+<?php
+
+namespace Gedmo\Translatable\Entity;
+
+use Doctrine\ORM\Mapping\Column;
+use Doctrine\ORM\Mapping\MappedSuperclass;
+use Doctrine\ORM\Mapping\Id;
+use Doctrine\ORM\Mapping\GeneratedValue;
+
+/**
+ * Gedmo\Translatable\Entity\AbstractPersonalTranslation
+ *
+ * @MappedSuperclass
+ */
+abstract class AbstractPersonalTranslation
+{
+    /**
+     * @var integer $id
+     *
+     * @Column(type="integer")
+     * @Id
+     * @GeneratedValue
+     */
+    protected $id;
+
+    /**
+     * @var string $locale
+     *
+     * @Column(type="string", length=8)
+     */
+    protected $locale;
+
+    /**
+     * @var string $field
+     *
+     * @Column(type="string", length=32)
+     */
+    protected $field;
+
+    /**
+     * Related entity with ManyToOne relation
+     * must be mapped by user
+     */
+    protected $object;
+
+    /**
+     * @var text $content
+     *
+     * @Column(type="text", nullable=true)
+     */
+    protected $content;
+
+    /**
+     * Get id
+     *
+     * @return integer $id
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set locale
+     *
+     * @param string $locale
+     * @return AbstractTranslation
+     */
+    public function setLocale($locale)
+    {
+        $this->locale = $locale;
+        return $this;
+    }
+
+    /**
+     * Get locale
+     *
+     * @return string $locale
+     */
+    public function getLocale()
+    {
+        return $this->locale;
+    }
+
+    /**
+     * Set field
+     *
+     * @param string $field
+     * @return AbstractTranslation
+     */
+    public function setField($field)
+    {
+        $this->field = $field;
+        return $this;
+    }
+
+    /**
+     * Get field
+     *
+     * @return string $field
+     */
+    public function getField()
+    {
+        return $this->field;
+    }
+
+    /**
+     * Set object related
+     *
+     * @param string $object
+     * @return AbstractTranslation
+     */
+    public function setObject($object)
+    {
+        $this->object = $object;
+        return $this;
+    }
+
+    /**
+     * Get related object
+     *
+     * @return object $object
+     */
+    public function getObject()
+    {
+        return $this->object;
+    }
+
+    /**
+     * Set content
+     *
+     * @param text $content
+     * @return AbstractTranslation
+     */
+    public function setContent($content)
+    {
+        $this->content = $content;
+        return $this;
+    }
+
+    /**
+     * Get content
+     *
+     * @return text $content
+     */
+    public function getContent()
+    {
+        return $this->content;
+    }
+}

+ 46 - 20
lib/Gedmo/Translatable/Mapping/Event/Adapter/ORM.php

@@ -3,8 +3,6 @@
 namespace Gedmo\Translatable\Mapping\Event\Adapter;
 
 use Gedmo\Mapping\Event\Adapter\ORM as BaseAdapterORM;
-use Doctrine\ORM\EntityManager;
-use Doctrine\ORM\Mapping\ClassMetadataInfo;
 use Doctrine\ORM\Query;
 use Gedmo\Translatable\Mapping\Event\TranslatableAdapter;
 use Gedmo\Tool\Wrapper\AbstractWrapper;
@@ -22,6 +20,19 @@ use Doctrine\DBAL\Types\Type;
  */
 final class ORM extends BaseAdapterORM implements TranslatableAdapter
 {
+    /**
+     * {@inheritDoc}
+     */
+    public function usesPersonalTranslation($translationClassName)
+    {
+        return $this
+            ->getObjectManager()
+            ->getClassMetadata($translationClassName)
+            ->getReflectionClass()
+            ->isSubclassOf('Gedmo\Translatable\Entity\AbstractPersonalTranslation')
+        ;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -54,23 +65,29 @@ final class ORM extends BaseAdapterORM implements TranslatableAdapter
     /**
      * {@inheritDoc}
      */
-    public function findTranslation($objectId, $objectClass, $locale, $field, $translationClass)
+    public function findTranslation(AbstractWrapper $wrapped, $locale, $field, $translationClass)
     {
         $em = $this->getObjectManager();
         $qb = $em->createQueryBuilder();
         $qb->select('trans')
             ->from($translationClass, 'trans')
             ->where(
-                'trans.foreignKey = :objectId',
                 'trans.locale = :locale',
-                'trans.field = :field',
-                'trans.objectClass = :objectClass'
-            );
+                'trans.field = :field'
+            )
+        ;
+        $qb->setParameters(compact('locale', 'field'));
+        if ($this->usesPersonalTranslation($translationClass)) {
+            $qb->andWhere('trans.object = :object');
+            $qb->setParameter('object', $wrapped->getObject());
+        } else {
+            $qb->andWhere('trans.foreignKey = :objectId');
+            $qb->andWhere('trans.objectClass = :objectClass');
+            $qb->setParameter('objectId', $wrapped->getIdentifier());
+            $qb->setParameter('objectClass', $wrapped->getMetadata()->name);
+        }
         $q = $qb->getQuery();
-        $result = $q->execute(
-            compact('field', 'locale', 'objectId', 'objectClass'),
-            Query::HYDRATE_OBJECT
-        );
+        $result = $q->getResult();
 
         if ($result) {
             return array_shift($result);
@@ -81,16 +98,25 @@ final class ORM extends BaseAdapterORM implements TranslatableAdapter
     /**
      * {@inheritDoc}
      */
-    public function removeAssociatedTranslations($objectId, $transClass, $targetClass)
+    public function removeAssociatedTranslations(AbstractWrapper $wrapped, $transClass)
     {
-        $em = $this->getObjectManager();
-        $dql = 'DELETE ' . $transClass . ' trans';
-        $dql .= ' WHERE trans.foreignKey = :objectId';
-        $dql .= ' AND trans.objectClass = :targetClass';
-
-        $q = $em->createQuery($dql);
-        $q->setParameters(compact('objectId', 'targetClass'));
-        return $q->getSingleScalarResult();
+        $qb = $this
+            ->getObjectManager()
+            ->createQueryBuilder()
+            ->delete($transClass, 'trans')
+        ;
+        if ($this->usesPersonalTranslation($transClass)) {
+            $qb->where('trans.object = :object');
+            $qb->setParameter('object', $wrapped->getObject());
+        } else {
+            $qb->where(
+                'trans.foreignKey = :objectId',
+                'trans.objectClass = :class'
+            );
+            $qb->setParameter('objectId', $wrapped->getIdentifier());
+            $qb->setParameter('class', $wrapped->getMetadata()->name);
+        }
+        return $qb->getQuery()->getSingleScalarResult();
     }
 
     /**

+ 14 - 6
lib/Gedmo/Translatable/Mapping/Event/TranslatableAdapter.php

@@ -3,6 +3,7 @@
 namespace Gedmo\Translatable\Mapping\Event;
 
 use Gedmo\Mapping\Event\AdapterInterface;
+use Gedmo\Tool\Wrapper\AbstractWrapper;
 
 /**
  * Doctrine event adapter interface
@@ -16,6 +17,15 @@ use Gedmo\Mapping\Event\AdapterInterface;
  */
 interface TranslatableAdapter extends AdapterInterface
 {
+    /**
+     * Checks if $translationClassName is a subclass
+     * of personal translation
+     *
+     * @param string $translationClassName
+     * @return boolean
+     */
+    function usesPersonalTranslation($translationClassName);
+
     /**
      * Get default LogEntry class used to store the logs
      *
@@ -36,24 +46,22 @@ interface TranslatableAdapter extends AdapterInterface
     /**
      * Search for existing translation record
      *
-     * @param mixed $objectId
-     * @param string $objectClass
+     * AbstractWrapper $wrapped
      * @param string $locale
      * @param string $field
      * @param string $translationClass
      * @return mixed - null if nothing is found, Translation otherwise
      */
-    function findTranslation($objectId, $objectClass, $locale, $field, $translationClass);
+    function findTranslation(AbstractWrapper $wrapped, $locale, $field, $translationClass);
 
     /**
      * Removes all associated translations for given object
      *
-     * @param mixed $objectId
+     * AbstractWrapper $wrapped
      * @param string $transClass
-     * @param string $targetClass
      * @return void
      */
-    function removeAssociatedTranslations($objectId, $transClass, $targetClass);
+    function removeAssociatedTranslations(AbstractWrapper $wrapped, $transClass);
 
     /**
      * Inserts the translation record

+ 33 - 29
lib/Gedmo/Translatable/TranslatableListener.php

@@ -3,9 +3,9 @@
 namespace Gedmo\Translatable;
 
 use Gedmo\Tool\Wrapper\AbstractWrapper;
-use Doctrine\Common\EventArgs,
-    Gedmo\Mapping\MappedEventSubscriber,
-    Gedmo\Translatable\Mapping\Event\TranslatableAdapter;
+use Doctrine\Common\EventArgs;
+use Gedmo\Mapping\MappedEventSubscriber;
+use Gedmo\Translatable\Mapping\Event\TranslatableAdapter;
 
 /**
  * The translation listener handles the generation and
@@ -148,6 +148,22 @@ class TranslatableListener extends MappedEventSubscriber
         $this->loadMetadataForObjectClass($ea->getObjectManager(), $eventArgs->getClassMetadata());
     }
 
+    /**
+     * Get the translation class to be used
+     * for the object $class
+     *
+     * @param TranslatableAdapter $ea
+     * @param string $class
+     * @return string
+     */
+    public function getTranslationClass(TranslatableAdapter $ea, $class)
+    {
+        return isset($this->configurations[$class]['translationClass']) ?
+            $this->configurations[$class]['translationClass'] :
+            $ea->getDefaultTranslationClass()
+        ;
+    }
+
     /**
      * Enable or disable translation fallback
      * to original record value
@@ -172,21 +188,6 @@ class TranslatableListener extends MappedEventSubscriber
         return $this->translationFallback;
     }
 
-    /**
-     * Get the translation class to be used
-     * for the object $class
-     *
-     * @param TranslatableAdapter $ea
-     * @param string $class
-     * @return string
-     */
-    public function getTranslationClass(TranslatableAdapter $ea, $class)
-    {
-        return isset($this->configurations[$class]['translationClass']) ?
-            $this->configurations[$class]['translationClass'] :
-            $ea->getDefaultTranslationClass();
-    }
-
     /**
      * Set the locale to use for translation listener
      *
@@ -302,7 +303,7 @@ class TranslatableListener extends MappedEventSubscriber
             if (isset($config['fields'])) {
                 $wrapped = AbstractWrapper::wrapp($object, $om);
                 $transClass = $this->getTranslationClass($ea, $meta->name);
-                $ea->removeAssociatedTranslations($wrapped->getIdentifier(), $transClass, $meta->name);
+                $ea->removeAssociatedTranslations($wrapped, $transClass);
             }
         }
     }
@@ -350,13 +351,14 @@ class TranslatableListener extends MappedEventSubscriber
         $object = $ea->getObject();
         $meta = $om->getClassMetadata(get_class($object));
         $config = $this->getConfiguration($om, $meta->name);
+        $translationClass = $this->getTranslationClass($ea, $meta->name);
         if (isset($config['fields'])) {
             $locale = $this->getTranslatableLocale($object, $meta);
             $oid = spl_object_hash($object);
             $this->translatedInLocale[$oid] = $locale;
         }
 
-        if ($this->skipOnLoad) {
+        if ($this->skipOnLoad || $ea->usesPersonalTranslation($translationClass)) {
             return;
         }
 
@@ -364,7 +366,7 @@ class TranslatableListener extends MappedEventSubscriber
             // fetch translations
             $result = $ea->loadTranslations(
                 $object,
-                $this->getTranslationClass($ea, $meta->name),
+                $translationClass,
                 $locale
             );
             // translate object's translatable properties
@@ -428,6 +430,7 @@ class TranslatableListener extends MappedEventSubscriber
         $om = $ea->getObjectManager();
         $wrapped = AbstractWrapper::wrapp($object, $om);
         $meta = $wrapped->getMetadata();
+        $config = $this->getConfiguration($om, $meta->name);
         // no need cache, metadata is loaded only once in MetadataFactoryClass
         $translationClass = $this->getTranslationClass($ea, $meta->name);
         $translationMetadata = $om->getClassMetadata($translationClass);
@@ -441,7 +444,6 @@ class TranslatableListener extends MappedEventSubscriber
         $oid = spl_object_hash($object);
         $changeSet = $ea->getObjectChangeSet($uow, $object);
 
-        $config = $this->getConfiguration($om, $meta->name);
         $translatableFields = $config['fields'];
         foreach ($translatableFields as $field) {
             $skip = isset($this->translatedInLocale[$oid]) && $locale === $this->translatedInLocale[$oid];
@@ -453,8 +455,7 @@ class TranslatableListener extends MappedEventSubscriber
             // check if translation allready is created
             if (!$isInsert) {
                 $translation = $ea->findTranslation(
-                    $objectId,
-                    $meta->name,
+                    $wrapped,
                     $locale,
                     $field,
                     $translationClass
@@ -465,8 +466,12 @@ class TranslatableListener extends MappedEventSubscriber
                 $translation = new $translationClass();
                 $translation->setLocale($locale);
                 $translation->setField($field);
-                $translation->setObjectClass($meta->name);
-                $translation->setForeignKey($objectId);
+                if ($ea->usesPersonalTranslation($translationClass)) {
+                    $translation->setObject($object);
+                } else {
+                    $translation->setObjectClass($meta->name);
+                    $translation->setForeignKey($objectId);
+                }
                 $scheduleUpdate = !$isInsert;
             }
 
@@ -474,7 +479,7 @@ class TranslatableListener extends MappedEventSubscriber
                 // set the translated field, take value using reflection
                 $value = $wrapped->getPropertyValue($field);
                 $translation->setContent($ea->getTranslationValue($object, $field));
-                if ($isInsert && !$objectId) {
+                if ($isInsert && !$objectId && !$ea->usesPersonalTranslation($translationClass)) {
                     // if we do not have the primary key yet available
                     // keep this translation in memory to insert it later with foreign key
                     $this->pendingTranslationInserts[spl_object_hash($object)][] = $translation;
@@ -500,8 +505,7 @@ class TranslatableListener extends MappedEventSubscriber
                 }
             }
             // cleanup current changeset only if working in a another locale different than de default one, otherwise the changeset will always be reverted
-            if($locale !== $this->defaultLocale)
-            {
+            if ($locale !== $this->defaultLocale) {
                 $ea->clearObjectChangeSet($uow, $oid);
                 // recompute changeset only if there are changes other than reverted translations
                 if ($modifiedChangeSet) {

+ 41 - 0
tests/Gedmo/Translatable/Fixture/Personal/Article.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Translatable\Fixture\Personal;
+
+use Gedmo\Mapping\Annotation as Gedmo;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @Gedmo\TranslationEntity(class="Translatable\Fixture\Personal\PersonalArticleTranslation")
+ * @ORM\Entity
+ */
+class Article
+{
+    /**
+     * @ORM\Id
+     * @ORM\GeneratedValue
+     * @ORM\Column(type="integer")
+     */
+    private $id;
+
+    /**
+     * @Gedmo\Translatable
+     * @ORM\Column(length=128)
+     */
+    private $title;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+}

+ 17 - 0
tests/Gedmo/Translatable/Fixture/Personal/PersonalArticleTranslation.php

@@ -0,0 +1,17 @@
+<?php
+namespace Translatable\Fixture\Personal;
+
+use Doctrine\ORM\Mapping as ORM;
+use Gedmo\Translatable\Entity\AbstractPersonalTranslation;
+
+/**
+ * @ORM\Table(name="article_translations")
+ * @ORM\Entity(repositoryClass="Gedmo\Translatable\Entity\Repository\TranslationRepository")
+ */
+class PersonalArticleTranslation extends AbstractPersonalTranslation
+{
+    /**
+     * @ORM\ManyToOne(targetEntity="Article", inversedBy="translations")
+     */
+    protected $entity;
+}

+ 71 - 0
tests/Gedmo/Translatable/PersonalTranslationTest.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace Gedmo\Translatable;
+
+use Doctrine\Common\EventManager;
+use Tool\BaseTestCaseORM;
+use Translatable\Fixture\Personal\Article;
+use Translatable\Fixture\Personal\PersonalArticleTranslation;
+
+/**
+ * These are tests for translatable behavior
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Translatable
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class PersonalTranslationTest extends BaseTestCaseORM
+{
+    const ARTICLE = 'Translatable\Fixture\Personal\Article';
+    const TRANSLATION = 'Translatable\Fixture\Personal\PersonalArticleTranslation';
+
+    private $translatableListener;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $evm = new EventManager;
+        $this->translatableListener = new TranslatableListener();
+        $this->translatableListener->setTranslatableLocale('en');
+        $this->translatableListener->setDefaultLocale('en');
+        $evm->addEventSubscriber($this->translatableListener);
+
+        $conn = array(
+            'driver' => 'pdo_mysql',
+            'host' => '127.0.0.1',
+            'dbname' => 'test',
+            'user' => 'root',
+            'password' => 'nimda'
+        );
+        //$this->getMockCustomEntityManager($conn, $evm);
+        $this->getMockSqliteEntityManager($evm);
+    }
+
+    /**
+     * @test
+     */
+    function shouldCreateTranslations()
+    {
+        $article = new Article;
+        $article->setTitle('en');
+
+        $this->em->persist($article);
+        $this->em->flush();
+
+        $this->translatableListener->setTranslatableLocale('de');
+        $article->setTitle('de');
+
+        $this->em->persist($article);
+        $this->em->flush();
+    }
+
+    protected function getUsedEntityFixtures()
+    {
+        return array(
+            self::ARTICLE,
+            self::TRANSLATION
+        );
+    }
+}