vendor/gedmo/doctrine-extensions/src/Translatable/Mapping/Event/Adapter/ORM.php line 64

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Doctrine Behavioral Extensions package.
  4.  * (c) Gediminas Morkevicius <gediminas.morkevicius@gmail.com> http://www.gediminasm.org
  5.  * For the full copyright and license information, please view the LICENSE
  6.  * file that was distributed with this source code.
  7.  */
  8. namespace Gedmo\Translatable\Mapping\Event\Adapter;
  9. use Doctrine\Common\Proxy\Proxy;
  10. use Doctrine\DBAL\Types\Type;
  11. use Doctrine\DBAL\Types\Types;
  12. use Doctrine\ORM\Mapping\ClassMetadata as EntityClassMetadata;
  13. use Doctrine\ORM\Mapping\ClassMetadataInfo as LegacyEntityClassMetadata;
  14. use Gedmo\Exception\RuntimeException;
  15. use Gedmo\Mapping\Event\Adapter\ORM as BaseAdapterORM;
  16. use Gedmo\Tool\Wrapper\AbstractWrapper;
  17. use Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation;
  18. use Gedmo\Translatable\Entity\Translation;
  19. use Gedmo\Translatable\Mapping\Event\TranslatableAdapter;
  20. /**
  21.  * Doctrine event adapter for ORM adapted
  22.  * for Translatable behavior
  23.  *
  24.  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  25.  */
  26. final class ORM extends BaseAdapterORM implements TranslatableAdapter
  27. {
  28.     public function usesPersonalTranslation($translationClassName)
  29.     {
  30.         return $this
  31.             ->getObjectManager()
  32.             ->getClassMetadata($translationClassName)
  33.             ->getReflectionClass()
  34.             ->isSubclassOf(AbstractPersonalTranslation::class)
  35.         ;
  36.     }
  37.     public function getDefaultTranslationClass()
  38.     {
  39.         return Translation::class;
  40.     }
  41.     public function loadTranslations($object$translationClass$locale$objectClass)
  42.     {
  43.         $em $this->getObjectManager();
  44.         $wrapped AbstractWrapper::wrap($object$em);
  45.         $result = [];
  46.         if ($this->usesPersonalTranslation($translationClass)) {
  47.             // first try to load it using collection
  48.             $found false;
  49.             $metadata $wrapped->getMetadata();
  50.             assert($metadata instanceof EntityClassMetadata || $metadata instanceof LegacyEntityClassMetadata);
  51.             foreach ($metadata->getAssociationMappings() as $assoc) {
  52.                 $isRightCollection $assoc['targetEntity'] === $translationClass
  53.                     && 'object' === $assoc['mappedBy']
  54.                     && EntityClassMetadata::ONE_TO_MANY === $assoc['type']
  55.                 ;
  56.                 if ($isRightCollection) {
  57.                     $collection $wrapped->getPropertyValue($assoc['fieldName']);
  58.                     foreach ($collection as $trans) {
  59.                         if ($trans->getLocale() === $locale) {
  60.                             $result[] = [
  61.                                 'field' => $trans->getField(),
  62.                                 'content' => $trans->getContent(),
  63.                             ];
  64.                         }
  65.                     }
  66.                     $found true;
  67.                     break;
  68.                 }
  69.             }
  70.             // if collection is not set, fetch it through relation
  71.             if (!$found) {
  72.                 $dql 'SELECT t.content, t.field FROM '.$translationClass.' t';
  73.                 $dql .= ' WHERE t.locale = :locale';
  74.                 $dql .= ' AND t.object = :object';
  75.                 $q $em->createQuery($dql);
  76.                 $q->setParameters([
  77.                     'object' => $object,
  78.                     'locale' => $locale,
  79.                 ]);
  80.                 $result $q->getArrayResult();
  81.             }
  82.         } else {
  83.             // load translated content for all translatable fields
  84.             $objectId $this->foreignKey($wrapped->getIdentifier(), $translationClass);
  85.             // construct query
  86.             $dql 'SELECT t.content, t.field FROM '.$translationClass.' t';
  87.             $dql .= ' WHERE t.foreignKey = :objectId';
  88.             $dql .= ' AND t.locale = :locale';
  89.             $dql .= ' AND t.objectClass = :objectClass';
  90.             // fetch results
  91.             $q $em->createQuery($dql);
  92.             $q->setParameters([
  93.                 'objectId' => $objectId,
  94.                 'locale' => $locale,
  95.                 'objectClass' => $objectClass,
  96.             ]);
  97.             $result $q->getArrayResult();
  98.         }
  99.         return $result;
  100.     }
  101.     public function findTranslation(AbstractWrapper $wrapped$locale$field$translationClass$objectClass)
  102.     {
  103.         $em $this->getObjectManager();
  104.         // first look in identityMap, will save one SELECT query
  105.         foreach ($em->getUnitOfWork()->getIdentityMap() as $className => $objects) {
  106.             if ($className === $translationClass) {
  107.                 foreach ($objects as $trans) {
  108.                     $isRequestedTranslation = !$trans instanceof Proxy
  109.                         && $trans->getLocale() === $locale
  110.                         && $trans->getField() === $field
  111.                     ;
  112.                     if ($isRequestedTranslation) {
  113.                         if ($this->usesPersonalTranslation($translationClass)) {
  114.                             $isRequestedTranslation $trans->getObject() === $wrapped->getObject();
  115.                         } else {
  116.                             $objectId $this->foreignKey($wrapped->getIdentifier(), $translationClass);
  117.                             $isRequestedTranslation $trans->getForeignKey() === $objectId
  118.                                 && $trans->getObjectClass() === $wrapped->getMetadata()->getName()
  119.                             ;
  120.                         }
  121.                     }
  122.                     if ($isRequestedTranslation) {
  123.                         return $trans;
  124.                     }
  125.                 }
  126.             }
  127.         }
  128.         $qb $em->createQueryBuilder();
  129.         $qb->select('trans')
  130.             ->from($translationClass'trans')
  131.             ->where(
  132.                 'trans.locale = :locale',
  133.                 'trans.field = :field'
  134.             )
  135.             ->setParameter('locale'$locale)
  136.             ->setParameter('field'$field)
  137.         ;
  138.         if ($this->usesPersonalTranslation($translationClass)) {
  139.             $qb->andWhere('trans.object = :object');
  140.             if ($wrapped->getIdentifier()) {
  141.                 $qb->setParameter('object'$wrapped->getObject());
  142.             } else {
  143.                 $qb->setParameter('object'null);
  144.             }
  145.         } else {
  146.             $qb->andWhere('trans.foreignKey = :objectId');
  147.             $qb->andWhere('trans.objectClass = :objectClass');
  148.             $qb->setParameter('objectId'$this->foreignKey($wrapped->getIdentifier(), $translationClass));
  149.             $qb->setParameter('objectClass'$objectClass);
  150.         }
  151.         $q $qb->getQuery();
  152.         $q->setMaxResults(1);
  153.         return $q->getOneOrNullResult();
  154.     }
  155.     public function removeAssociatedTranslations(AbstractWrapper $wrapped$transClass$objectClass)
  156.     {
  157.         $qb $this
  158.             ->getObjectManager()
  159.             ->createQueryBuilder()
  160.             ->delete($transClass'trans')
  161.         ;
  162.         if ($this->usesPersonalTranslation($transClass)) {
  163.             $qb->where('trans.object = :object');
  164.             $qb->setParameter('object'$wrapped->getObject());
  165.         } else {
  166.             $qb->where(
  167.                 'trans.foreignKey = :objectId',
  168.                 'trans.objectClass = :class'
  169.             );
  170.             $qb->setParameter('objectId'$this->foreignKey($wrapped->getIdentifier(), $transClass));
  171.             $qb->setParameter('class'$objectClass);
  172.         }
  173.         return $qb->getQuery()->getSingleScalarResult();
  174.     }
  175.     public function insertTranslationRecord($translation)
  176.     {
  177.         $em $this->getObjectManager();
  178.         $meta $em->getClassMetadata(get_class($translation));
  179.         $data = [];
  180.         foreach ($meta->getReflectionProperties() as $fieldName => $reflProp) {
  181.             if (!$meta->isIdentifier($fieldName)) {
  182.                 $data[$meta->getColumnName($fieldName)] = $reflProp->getValue($translation);
  183.             }
  184.         }
  185.         $table $meta->getTableName();
  186.         if (!$em->getConnection()->insert($table$data)) {
  187.             throw new RuntimeException('Failed to insert new Translation record');
  188.         }
  189.     }
  190.     public function getTranslationValue($object$field$value false)
  191.     {
  192.         $em $this->getObjectManager();
  193.         $wrapped AbstractWrapper::wrap($object$em);
  194.         $meta $wrapped->getMetadata();
  195.         if (false === $value) {
  196.             $value $wrapped->getPropertyValue($field);
  197.         }
  198.         return $em->getConnection()->convertToDatabaseValue($value$meta->getTypeOfField($field));
  199.     }
  200.     public function setTranslationValue($object$field$value)
  201.     {
  202.         $em $this->getObjectManager();
  203.         $wrapped AbstractWrapper::wrap($object$em);
  204.         $meta $wrapped->getMetadata();
  205.         $value $em->getConnection()->convertToPHPValue($value$meta->getTypeOfField($field));
  206.         $wrapped->setPropertyValue($field$value);
  207.     }
  208.     /**
  209.      * Transforms foreign key of translation to appropriate PHP value
  210.      * to prevent database level cast
  211.      *
  212.      * @param mixed  $key       foreign key value
  213.      * @param string $className translation class name
  214.      *
  215.      * @phpstan-param class-string $className translation class name
  216.      *
  217.      * @return int|string transformed foreign key
  218.      */
  219.     private function foreignKey($keystring $className)
  220.     {
  221.         $em $this->getObjectManager();
  222.         $meta $em->getClassMetadata($className);
  223.         $type Type::getType($meta->getTypeOfField('foreignKey'));
  224.         switch (Type::lookupName($type)) {
  225.             case Types::BIGINT:
  226.             case Types::INTEGER:
  227.             case Types::SMALLINT:
  228.                 return (int) $key;
  229.             default:
  230.                 return (string) $key;
  231.         }
  232.     }
  233. }