diff --git a/lib/FSi/DoctrineExtensions/Translatable/ClassTranslationContext.php b/lib/FSi/DoctrineExtensions/Translatable/ClassTranslationContext.php new file mode 100644 index 0000000..1713b5c --- /dev/null +++ b/lib/FSi/DoctrineExtensions/Translatable/ClassTranslationContext.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FSi\DoctrineExtensions\Translatable; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\ObjectManager; +use FSi\DoctrineExtensions\Translatable\Mapping\TranslationAssociationMetadata; +use FSi\DoctrineExtensions\Translatable\Model\TranslatableRepositoryInterface; + +/** + * @internal + */ +class ClassTranslationContext +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ClassMetadata + */ + private $classMetadata; + + /** + * @var TranslationAssociationMetadata + */ + private $associationMetadata; + + public function __construct( + ObjectManager $objectManager, + ClassMetadata $classMetadata, + TranslationAssociationMetadata $associationMetadata + ) { + $this->objectManager = $objectManager; + $this->classMetadata = $classMetadata; + $this->associationMetadata = $associationMetadata; + } + + /** + * @return ClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + /** + * @return Mapping\ClassMetadata + */ + public function getTranslatableMetadata() + { + return $this->associationMetadata->getClassMetadata(); + } + + /** + * @return TranslationAssociationMetadata + */ + public function getAssociationMetadata() + { + return $this->associationMetadata; + } + + /** + * @return ClassMetadata + */ + public function getTranslationMetadata() + { + $associationName = $this->associationMetadata->getAssociationName(); + $translationClass = $this->classMetadata->getAssociationTargetClass($associationName); + return $this->objectManager->getClassMetadata($translationClass); + } + + /** + * @return TranslatableRepositoryInterface + * @throws Exception\AnnotationException + */ + public function getTranslatableRepository() + { + $repository = $this->objectManager->getRepository($this->classMetadata->getName()); + + if (!($repository instanceof TranslatableRepositoryInterface)) { + throw new Exception\AnnotationException(sprintf( + 'Entity "%s" has "%s" as its "repositoryClass" which does not implement \FSi\DoctrineExtensions\Translatable\Model\TranslatableRepositoryInterface', + $this->classMetadata->getName(), + get_class($repository) + )); + } + + return $repository; + } + + /** + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/lib/FSi/DoctrineExtensions/Translatable/Entity/Repository/TranslatableRepository.php b/lib/FSi/DoctrineExtensions/Translatable/Entity/Repository/TranslatableRepository.php index 4cba773..fe9c461 100644 --- a/lib/FSi/DoctrineExtensions/Translatable/Entity/Repository/TranslatableRepository.php +++ b/lib/FSi/DoctrineExtensions/Translatable/Entity/Repository/TranslatableRepository.php @@ -150,6 +150,31 @@ public function findTranslation($object, $locale, $translationAssociation = 'tra } } + /** + * @param object $object + * @param string $translationAssociation + * @return \Doctrine\Common\Collections\Collection + * @throws \FSi\DoctrineExtensions\Translatable\Exception\RuntimeException + */ + public function getTranslations($object, $translationAssociation = 'translations') + { + $translations = $this->getClassMetadata()->getFieldValue($object, $translationAssociation); + + if ($translations === null) { + return new ArrayCollection(); + } + + if (!($translations instanceof Collection)) { + throw new RuntimeException(sprintf( + 'Entity %s must contains implementation of "Doctrine\Common\Collections\Collection" in "%s" association', + $this->getClassName(), + $translationAssociation + )); + } + + return $translations; + } + /** * {@inheritdoc} */ @@ -324,31 +349,6 @@ protected function areTranslationsIndexedByLocale($translationAssociation) return ($translationAssociationMapping['indexBy'] == $translationExtendedMeta->localeProperty); } - /** - * @param object $object - * @param string $translationAssociation - * @return \Doctrine\Common\Collections\Collection - * @throws \FSi\DoctrineExtensions\Translatable\Exception\RuntimeException - */ - protected function getTranslations($object, $translationAssociation) - { - $translations = $this->getClassMetadata()->getFieldValue($object, $translationAssociation); - - if ($translations === null) { - return new ArrayCollection(); - } - - if (!($translations instanceof Collection)) { - throw new RuntimeException(sprintf( - 'Entity %s must contains implementation of "Doctrine\Common\Collections\Collection" in "%s" association', - $this->getClassName(), - $translationAssociation - )); - } - - return $translations; - } - /** * @param object $translation * @param string $translationAssociation diff --git a/lib/FSi/DoctrineExtensions/Translatable/Mapping/ClassMetadata.php b/lib/FSi/DoctrineExtensions/Translatable/Mapping/ClassMetadata.php index 533ad24..afa4c82 100644 --- a/lib/FSi/DoctrineExtensions/Translatable/Mapping/ClassMetadata.php +++ b/lib/FSi/DoctrineExtensions/Translatable/Mapping/ClassMetadata.php @@ -10,6 +10,7 @@ namespace FSi\DoctrineExtensions\Translatable\Mapping; use FSi\Component\Metadata\AbstractClassMetadata; +use Doctrine\Common\Persistence\Mapping\ClassMetadata as DoctrineClassMetadata; class ClassMetadata extends AbstractClassMetadata { @@ -61,4 +62,18 @@ public function getTranslatableProperties() { return $this->translatableProperties; } + + /** + * @return TranslationAssociationMetadata[] + */ + public function getTranslationAssociationMetadatas() + { + $metadatas = array(); + + foreach ($this->getTranslatableProperties() as $association => $properties) { + $metadatas[] = new TranslationAssociationMetadata($this, $association, $properties); + } + + return $metadatas; + } } diff --git a/lib/FSi/DoctrineExtensions/Translatable/Mapping/Driver/Annotation.php b/lib/FSi/DoctrineExtensions/Translatable/Mapping/Driver/Annotation.php index 3370c28..243bedb 100644 --- a/lib/FSi/DoctrineExtensions/Translatable/Mapping/Driver/Annotation.php +++ b/lib/FSi/DoctrineExtensions/Translatable/Mapping/Driver/Annotation.php @@ -26,6 +26,7 @@ class Annotation extends AbstractAnnotationDriver protected function loadExtendedClassMetadata(ClassMetadata $baseClassMetadata, ClassMetadataInterface $extendedClassMetadata) { $classReflection = $extendedClassMetadata->getClassReflection(); + foreach ($classReflection->getProperties() as $property) { if ($baseClassMetadata->isMappedSuperclass && !$property->isPrivate() || $baseClassMetadata->isInheritedField($property->name) || diff --git a/lib/FSi/DoctrineExtensions/Translatable/Mapping/TranslationAssociationMetadata.php b/lib/FSi/DoctrineExtensions/Translatable/Mapping/TranslationAssociationMetadata.php new file mode 100644 index 0000000..495adb8 --- /dev/null +++ b/lib/FSi/DoctrineExtensions/Translatable/Mapping/TranslationAssociationMetadata.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FSi\DoctrineExtensions\Translatable\Mapping; + +class TranslationAssociationMetadata +{ + /** + * @var ClassMetadata + */ + private $classMetadata; + + /** + * @var string + */ + private $association; + + /** + * @var array + */ + private $properties; + + public function __construct(ClassMetadata $classMetadata, $association, $properties) + { + $this->classMetadata = $classMetadata; + $this->association = $association; + $this->properties = $properties; + } + + /** + * @return ClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + /** + * @return string + */ + public function getAssociationName() + { + return $this->association; + } + + /** + * @return array + */ + public function getProperties() + { + return $this->properties; + } +} diff --git a/lib/FSi/DoctrineExtensions/Translatable/Model/TranslatableRepositoryInterface.php b/lib/FSi/DoctrineExtensions/Translatable/Model/TranslatableRepositoryInterface.php index edf8f2f..cef375c 100644 --- a/lib/FSi/DoctrineExtensions/Translatable/Model/TranslatableRepositoryInterface.php +++ b/lib/FSi/DoctrineExtensions/Translatable/Model/TranslatableRepositoryInterface.php @@ -65,6 +65,14 @@ public function hasTranslation($object, $locale, $translationAssociation = 'tran */ public function getTranslation($object, $locale, $translationAssociation = 'translations'); + /** + * @param $object + * @param string $translationAssociation + * @return \Doctrine\Common\Collections\Collection + * @throws \FSi\DoctrineExtensions\Translatable\Exception\RuntimeException + */ + public function getTranslations($object, $translationAssociation = 'translations'); + /** * @param object $object * @param mixed $locale diff --git a/lib/FSi/DoctrineExtensions/Translatable/TranslatableListener.php b/lib/FSi/DoctrineExtensions/Translatable/TranslatableListener.php index 8d0e488..8bcca2a 100644 --- a/lib/FSi/DoctrineExtensions/Translatable/TranslatableListener.php +++ b/lib/FSi/DoctrineExtensions/Translatable/TranslatableListener.php @@ -9,19 +9,18 @@ namespace FSi\DoctrineExtensions\Translatable; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\EventArgs; -use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Event\PreFlushEventArgs; -use Doctrine\ORM\Proxy\Proxy; -use FSi\DoctrineExtensions\Translatable\Entity\Repository\TranslatableRepository; -use FSi\DoctrineExtensions\Translatable\Model\TranslatableRepositoryInterface; -use Symfony\Component\PropertyAccess\PropertyAccess; use FSi\Component\Metadata\ClassMetadataInterface; use FSi\DoctrineExtensions\Mapping\MappedEventSubscriber; +use FSi\DoctrineExtensions\Translatable\Entity\Repository\TranslatableRepository; use FSi\DoctrineExtensions\Translatable\Exception; use FSi\DoctrineExtensions\Translatable\Mapping\ClassMetadata as TranslatableClassMetadata; +use FSi\DoctrineExtensions\Translatable\Mapping\TranslationAssociationMetadata; +use FSi\DoctrineExtensions\Translatable\Model\TranslatableRepositoryInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; class TranslatableListener extends MappedEventSubscriber { @@ -44,6 +43,23 @@ class TranslatableListener extends MappedEventSubscriber */ private $_defaultLocale; + /** + * @var TranslationHelper + */ + private $translationHelper; + + /** + * @var array + */ + private $classTranslationContexts; + + /** + */ + public function __construct() + { + $this->translationHelper = new TranslationHelper($this->getPropertyAccessor()); + } + /** * Set the current locale * @@ -163,9 +179,9 @@ public function preFlush(PreFlushEventArgs $eventArgs) /** * Load translations fields into object properties * - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager + * @param ObjectManager $objectManager * @param object $object - * @param mixed $locale + * @param string $locale */ public function loadTranslation(ObjectManager $objectManager, $object, $locale) { @@ -174,9 +190,24 @@ public function loadTranslation(ObjectManager $objectManager, $object, $locale) return; } - $translatableProperties = $translatableMeta->getTranslatableProperties(); - foreach ($translatableProperties as $translationAssociation => $properties) { - $this->loadObjectTranslation($objectManager, $object, $translationAssociation, $locale); + foreach ($translatableMeta->getTranslationAssociationMetadatas() as $associationMeta) { + + $context = $this->getTranslationContext($objectManager, $associationMeta, $object); + $associationName = $associationMeta->getAssociationName(); + $repository = $context->getTranslatableRepository(); + $translation = $repository->findTranslation($object, $locale, $associationName); + + //default locale fallback + if (!isset($translation) && isset($this->_defaultLocale) && $this->_defaultLocale !== $locale) { + $locale = $this->_defaultLocale; + $translation = $repository->findTranslation($object, $this->_defaultLocale, $associationName); + } + + if (isset($translation)) { + $this->translationHelper->copyTranslationProperties($context, $object, $translation, $locale); + } else { + $this->translationHelper->clearTranslatableProperties($context, $object); + } } } @@ -267,68 +298,6 @@ private function validateTranslationLocaleProperty(ClassMetadata $baseClassMetad } } - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param mixed $locale - */ - private function loadObjectTranslation(ObjectManager $objectManager, $object, $translationAssociation, $locale) - { - if ($this->findAndLoadObjectTranslationByLocale($objectManager, $object, $translationAssociation, $locale)) { - return; - } - - if (isset($this->_defaultLocale) && - $this->findAndLoadObjectTranslationByLocale($objectManager, $object, $translationAssociation, $this->_defaultLocale)) { - return; - } - - $this->clearObjectProperties($objectManager, $object, $translationAssociation); - $this->setObjectLocale($objectManager, $object, null); - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param mixed $locale - * @return bool - */ - private function findAndLoadObjectTranslationByLocale(ObjectManager $objectManager, $object, $translationAssociation, $locale) - { - $translation = $this->getRepository($objectManager, $object) - ->findTranslation($object, $locale, $translationAssociation); - - if (!isset($translation)) { - return false; - } - - $this->copyTranslationFieldsToObjectProperties($objectManager, $object, $translationAssociation, $translation); - $this->setObjectLocale($objectManager, $object, $locale); - - return true; - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param object $translation - */ - private function copyTranslationFieldsToObjectProperties(ObjectManager $objectManager, $object, $translationAssociation, $translation) - { - $translatableProperties = $this->getTranslatableMetadata($objectManager, $object)->getTranslatableProperties(); - - foreach ($translatableProperties[$translationAssociation] as $property => $translationField) { - $this->getPropertyAccessor()->setValue( - $object, - $property, - $this->getPropertyAccessor()->getValue($translation, $translationField) - ); - } - } - /** * Helper method to insert, remove or update translations entities associated with specified object * @@ -342,250 +311,65 @@ private function updateObjectTranslations(ObjectManager $objectManager, $object) return; } - $translatableProperties = $translatableMeta->getTranslatableProperties(); - foreach ($translatableProperties as $translationAssociation => $properties) { - $this->updateObjectTranslation( - $objectManager, - $object, - $translationAssociation - ); - } - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - */ - private function updateObjectTranslation(ObjectManager $objectManager, $object, $translationAssociation) - { - $locale = $this->getObjectOrCurrentLocale($objectManager, $object, $translationAssociation); - - $translationToRemove = $this->getObjectTranslationToRemove($objectManager, $object, $translationAssociation, $locale); - if ($translationToRemove) { - $this->removeObjectTranslation($objectManager, $object, $translationAssociation, $translationToRemove); - return; - } - - if (!$this->hasObjectNotNullProperties($objectManager, $object, $translationAssociation)) { - return; - } - $this->copyObjectPropertiesToTranslationFields($objectManager, $object, $translationAssociation, $locale); - } + foreach ($translatableMeta->getTranslationAssociationMetadatas() as $associationMeta) { - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @return mixed - */ - private function getObjectOrCurrentLocale(ObjectManager $objectManager, $object, $translationAssociation) - { - $locale = $this->getObjectLocale($objectManager, $object); - if (!isset($locale)) { - $locale = $this->getLocale(); - } + $context = $this->getTranslationContext($objectManager, $associationMeta, $object); - $hasNotNullProperties = $this->hasObjectNotNullProperties($objectManager, $object, $translationAssociation); - if (!isset($locale) && $hasNotNullProperties) { - throw new Exception\RuntimeException( - "Neither object's locale nor the current locale was set for translatable properties" - ); - } - - return $locale; - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param mixed $locale - * @return object - */ - private function getObjectTranslationToRemove(ObjectManager $objectManager, $object, $translationAssociation, $locale) - { - $translation = $this->getRepository($objectManager, $object) - ->findTranslation($object, $locale, $translationAssociation); - $objectLocale = $this->getObjectLocale($objectManager, $object); - $hasNotNullProperties = $this->hasObjectNotNullProperties($objectManager, $object, $translationAssociation); - if (!$hasNotNullProperties && isset($translation) && isset($objectLocale)) { - return $translation; - } else { - return null; - } - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param object $currentTranslation - */ - private function removeObjectTranslation(ObjectManager $objectManager, $object, $translationAssociation, $currentTranslation) - { - $objectManager->remove($currentTranslation); - - $translations = $this->getObjectTranslations($object, $translationAssociation); - if ($translations->contains($currentTranslation)) { - $translations->removeElement($currentTranslation); - } - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param mixed $locale - */ - private function copyObjectPropertiesToTranslationFields(ObjectManager $objectManager, $object, $translationAssociation, $locale) - { - $translatableProperties = $this->getTranslatableMetadata($objectManager, $object)->getTranslatableProperties(); - foreach ($translatableProperties[$translationAssociation] as $property => $translationField) { - $propertyValue = $this->getPropertyAccessor()->getValue($object, $property); - - $translation = $this->findOrCreateObjectTranslation( - $objectManager, - $object, - $translationAssociation, - $locale - ); - - if ($this->getPropertyAccessor()->getValue($translation, $translationField) !== $propertyValue) { - $this->getPropertyAccessor()->setValue($translation, $translationField, $propertyValue); + $locale = $this->translationHelper->getObjectLocale($context, $object); + if (!isset($locale)) { + $locale = $this->getLocale(); } - } - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @param mixed $locale - * @return object - */ - private function findOrCreateObjectTranslation(ObjectManager $objectManager, $object, $translationAssociation, $locale) - { - $translation = $this->getRepository($objectManager, $object) - ->getTranslation($object, $locale, $translationAssociation); - if (!$objectManager->contains($translation)) { - $objectManager->persist($translation); - } - - return $translation; - } + $hasTranslatedProperties = $this->translationHelper->hasTranslatedProperties($context, $object); - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @throws Exception\AnnotationException - * @return TranslatableRepository - */ - private function getRepository(ObjectManager $objectManager, $object) - { - $meta = $this->getObjectClassMetadata($objectManager, $object); - $repository = $objectManager->getRepository($meta->getName()); - - if (!($repository instanceof TranslatableRepositoryInterface)) { - throw new Exception\AnnotationException(sprintf( - 'Entity "%s" has "%s" as its "repositoryClass" which does not implement \FSi\DoctrineExtensions\Translatable\Model\TranslatableRepositoryInterface', - $meta->getName(), - get_class($repository) - )); - } - - return $repository; - } + if (!isset($locale) && $hasTranslatedProperties) { + throw new Exception\RuntimeException( + "Neither object's locale nor the current locale was set for translatable properties" + ); + } - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @return array - */ - private function clearObjectProperties(ObjectManager $objectManager, $object, $translationAssociation) - { - $translatableProperties = $this->getTranslatableMetadata($objectManager, $object)->getTranslatableProperties(); - $translationMeta = $this->getTranslationClassMetadata($objectManager, $object, $translationAssociation); - foreach ($translatableProperties[$translationAssociation] as $property => $translationField) { - if ($translationMeta->isCollectionValuedAssociation($translationField)) { - $this->getPropertyAccessor()->setValue($object, $property, array()); + if ($hasTranslatedProperties) { + $this->translationHelper->copyPropertiesToTranslation( + $context, + $object, + $locale + ); } else { - $this->getPropertyAccessor()->setValue($object, $property, null); + $this->translationHelper->removeEmptyTranslation( + $context, + $object + ); } } } /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $translationAssociation - * @return bool + * @param ObjectManager $objectManager + * @param TranslationAssociationMetadata $associationMeta + * @param $object + * @return ClassTranslationContext */ - private function hasObjectNotNullProperties(ObjectManager $objectManager, $object, $translationAssociation) - { - $translatableProperties = $this - ->getTranslatableMetadata($objectManager, $object) - ->getTranslatableProperties(); - $translationMeta = $this->getTranslationClassMetadata($objectManager, $object, $translationAssociation); - - $hasNotNullProperties = false; - foreach ($translatableProperties[$translationAssociation] as $property => $translationField) { - $value = $this->getPropertyAccessor()->getValue($object, $property); - if ($translationMeta->isCollectionValuedAssociation($translationField) && count($value)) { - $hasNotNullProperties = true; - } elseif (!$translationMeta->isCollectionValuedAssociation($translationField) && (null !== $value)) { - $hasNotNullProperties = true; - } - } - - return $hasNotNullProperties; - } + private function getTranslationContext( + ObjectManager $objectManager, + TranslationAssociationMetadata $associationMeta, + $object + ) { + $classMeta = $this->getObjectClassMetadata($objectManager, $object); + $className = $classMeta->getName(); + $associationName = $associationMeta->getAssociationName(); - /** - * @param object $object - * @param string $translationAssociation - * @return \Doctrine\Common\Collections\ArrayCollection - */ - private function getObjectTranslations($object, $translationAssociation) - { - $translations = $this->getPropertyAccessor()->getValue($object, $translationAssociation); - if (!isset($translations)) { - $translations = new ArrayCollection(); + if (empty($this->classTranslationContexts[$className][$associationName])) { + $context = new ClassTranslationContext($objectManager, $classMeta, $associationMeta); + $this->classTranslationContexts[$className][$associationName] = $context; } - return $translations; + return $this->classTranslationContexts[$className][$associationName]; } /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager + * @param ObjectManager $objectManager * @param object $object - * @return mixed - */ - private function getObjectLocale(ObjectManager $objectManager, $object) - { - $localeProperty = $this->getTranslatableMetadata($objectManager, $object)->localeProperty; - - return $this->getPropertyAccessor()->getValue($object, $localeProperty); - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @param string $locale - */ - private function setObjectLocale(ObjectManager $objectManager, $object, $locale) - { - $localeProperty = $this->getTranslatableMetadata($objectManager, $object)->localeProperty; - - $this->getPropertyAccessor()->setValue($object, $localeProperty, $locale); - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata + * @return ClassMetadata */ private function getObjectClassMetadata(ObjectManager $objectManager, $object) { @@ -593,26 +377,13 @@ private function getObjectClassMetadata(ObjectManager $objectManager, $object) } /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager + * @param ObjectManager $objectManager * @param object $object - * @param string $translationAssociation - * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata - */ - private function getTranslationClassMetadata(ObjectManager $objectManager, $object, $translationAssociation) - { - $meta = $this->getObjectClassMetadata($objectManager, $object); - return $objectManager->getClassMetadata($meta->getAssociationTargetClass($translationAssociation)); - } - - /** - * @param \Doctrine\Common\Persistence\ObjectManager $objectManager - * @param object $object - * @return \FSi\DoctrineExtensions\Translatable\Mapping\ClassMetadata + * @return TranslatableClassMetadata */ private function getTranslatableMetadata(ObjectManager $objectManager, $object) { $meta = $this->getObjectClassMetadata($objectManager, $object); - return $this->getExtendedMetadata($objectManager, $meta->getName()); } } diff --git a/lib/FSi/DoctrineExtensions/Translatable/TranslationHelper.php b/lib/FSi/DoctrineExtensions/Translatable/TranslationHelper.php new file mode 100644 index 0000000..97c3b69 --- /dev/null +++ b/lib/FSi/DoctrineExtensions/Translatable/TranslationHelper.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FSi\DoctrineExtensions\Translatable; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\ObjectManager; +use FSi\DoctrineExtensions\Translatable\Mapping\ClassMetadata as TranslatableClassMetadata; +use FSi\DoctrineExtensions\Translatable\Model\TranslatableRepositoryInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessor; + +/** + * @internal + */ +class TranslationHelper +{ + /** + * @var PropertyAccessor + */ + private $propertyAccessor; + + /** + * @param PropertyAccessor|null $propertyAccessor + */ + public function __construct(PropertyAccessor $propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor; + } + + /** + * @param ClassTranslationContext $context + * @param $object + * @param $translation + * @param $locale + */ + public function copyTranslationProperties(ClassTranslationContext $context, $object, $translation, $locale) + { + $this->copyProperties($translation, $object, array_flip($context->getAssociationMetadata()->getProperties())); + $this->setObjectLocale($context->getTranslatableMetadata(), $object, $locale); + } + + /** + * @param TranslatableRepositoryInterface $translatableRepository + * @param ClassTranslationContext $context + * @param object $object + * @param string $defaultLocale + */ + public function copyPropertiesToTranslation( + ClassTranslationContext $context, + $object, + $defaultLocale + ) { + $translationAssociationMeta = $context->getAssociationMetadata(); + + $locale = $this->getObjectLocale($context, $object); + if (!isset($locale)) { + $locale = $defaultLocale; + } + + $translatableRepository = $context->getTranslatableRepository(); + $translation = $translatableRepository->getTranslation( + $object, + $locale, + $translationAssociationMeta->getAssociationName() + ); + + $objectManager = $context->getObjectManager(); + if (!$objectManager->contains($translation)) { + $objectManager->persist($translation); + } + + $this->copyProperties($object, $translation, $translationAssociationMeta->getProperties()); + } + + /** + * @param TranslatableRepositoryInterface $translatableRepository + * @param ClassTranslationContext $context + * @param object $object + */ + public function removeEmptyTranslation( + ClassTranslationContext $context, + $object + ) { + if ($this->hasTranslatedProperties($context, $object)) { + return; + } + + $objectLocale = $this->getObjectLocale($context, $object); + if (!isset($objectLocale)) { + return; + } + + $translationAssociationMeta = $context->getAssociationMetadata(); + $associationName = $translationAssociationMeta->getAssociationName(); + $translatableRepository = $context->getTranslatableRepository(); + $translation = $translatableRepository->findTranslation($object, $objectLocale, $associationName); + + if (!isset($translation)) { + return; + } + + $context->getObjectManager()->remove($translation); + + $translations = $translatableRepository->getTranslations($object, $associationName); + if ($translations->contains($translation)) { + $translations->removeElement($translation); + } + } + + /** + * @param ClassTranslationContext $context + * @param $object + */ + public function clearTranslatableProperties(ClassTranslationContext $context, $object) + { + $translationMeta = $context->getTranslationMetadata(); + $propertyAccessor = $this->getPropertyAccessor(); + + foreach ($context->getAssociationMetadata()->getProperties() as $property => $translationField) { + if ($translationMeta->isCollectionValuedAssociation($translationField)) { + $propertyAccessor->setValue($object, $property, array()); + } else { + $propertyAccessor->setValue($object, $property, null); + } + } + + $this->setObjectLocale($context->getTranslatableMetadata(), $object, null); + } + + /** + * @param ClassTranslationContext $context + * @param $object + * @return bool + */ + public function hasTranslatedProperties(ClassTranslationContext $context, $object) + { + $translationMeta = $context->getTranslationMetadata(); + $properties = $context->getAssociationMetadata()->getProperties(); + $propertyAccessor = $this->getPropertyAccessor(); + + foreach ($properties as $property => $translationField) { + $value = $propertyAccessor->getValue($object, $property); + if ($translationMeta->isCollectionValuedAssociation($translationField) && count($value) + || !$translationMeta->isCollectionValuedAssociation($translationField) && null !== $value + ) { + return true; + } + } + + return false; + } + + /** + * @param ClassTranslationContext $context + * @param $object + * @return string + */ + public function getObjectLocale(ClassTranslationContext $context, $object) + { + $localeProperty = $context->getTranslatableMetadata()->localeProperty; + return $this->getPropertyAccessor()->getValue($object, $localeProperty); + } + + /** + * @param TranslatableClassMetadata $classMetadata + * @param object $object + * @param string $locale + */ + private function setObjectLocale(TranslatableClassMetadata $classMetadata, $object, $locale) + { + $localeProperty = $classMetadata->localeProperty; + $this->getPropertyAccessor()->setValue($object, $localeProperty, $locale); + } + + /** + * @param object $source + * @param object $target + * @param array $properties + */ + private function copyProperties($source, $target, $properties) + { + $propertyAccessor = $this->getPropertyAccessor(); + + foreach ($properties as $sourceField => $targetField) { + $value = $propertyAccessor->getValue($source, $sourceField); + $propertyAccessor->setValue($target, $targetField, $value); + } + } + + /** + * @return \Symfony\Component\PropertyAccess\PropertyAccessor + */ + private function getPropertyAccessor() + { + if (!isset($this->propertyAccessor)) { + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); + } + + return $this->propertyAccessor; + } +}