vendor/shopware/platform/src/Core/Framework/DataAbstractionLayer/Dbal/EntityHydrator.php line 470

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\DataAbstractionLayer\Dbal;
  3. use Shopware\Core\Framework\Context;
  4. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  5. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  6. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Extension;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Inherited;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Field\ParentAssociationField;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Field\VersionField;
  19. use Shopware\Core\Framework\DataAbstractionLayer\PartialEntity;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommandQueue;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Write\DataStack\KeyValuePair;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Write\EntityExistence;
  23. use Shopware\Core\Framework\DataAbstractionLayer\Write\WriteContext;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Write\WriteParameterBag;
  25. use Shopware\Core\Framework\Struct\ArrayStruct;
  26. use Symfony\Component\DependencyInjection\ContainerInterface;
  27. /**
  28.  * Allows to hydrate database values into struct objects.
  29.  */
  30. class EntityHydrator
  31. {
  32.     protected static array $partial = [];
  33.     private static array $hydrated = [];
  34.     /**
  35.      * @var string[]
  36.      */
  37.     private static array $manyToOne = [];
  38.     private static array $translatedFields = [];
  39.     private ContainerInterface $container;
  40.     /**
  41.      * @psalm-suppress ContainerDependency
  42.      */
  43.     public function __construct(ContainerInterface $container)
  44.     {
  45.         $this->container $container;
  46.     }
  47.     public function hydrate(EntityCollection $collectionstring $entityClassEntityDefinition $definition, array $rowsstring $rootContext $context, array $partial = []): EntityCollection
  48.     {
  49.         self::$hydrated = [];
  50.         self::$partial $partial;
  51.         if (!empty(self::$partial)) {
  52.             $collection = new EntityCollection();
  53.         }
  54.         foreach ($rows as $row) {
  55.             $collection->add($this->hydrateEntity($definition$entityClass$row$root$context$partial));
  56.         }
  57.         return $collection;
  58.     }
  59.     final public static function createClass(string $class)
  60.     {
  61.         return new $class();
  62.     }
  63.     final public static function buildUniqueIdentifier(EntityDefinition $definition, array $rowstring $root): array
  64.     {
  65.         $primaryKeyFields $definition->getPrimaryKeys();
  66.         $primaryKey = [];
  67.         foreach ($primaryKeyFields as $field) {
  68.             if ($field instanceof VersionField || $field instanceof ReferenceVersionField) {
  69.                 continue;
  70.             }
  71.             $accessor $root '.' $field->getPropertyName();
  72.             $primaryKey[$field->getPropertyName()] = $field->getSerializer()->decode($field$row[$accessor]);
  73.         }
  74.         return $primaryKey;
  75.     }
  76.     final public static function encodePrimaryKey(EntityDefinition $definition, array $primaryKeyContext $context): array
  77.     {
  78.         $fields $definition->getPrimaryKeys();
  79.         $mapped = [];
  80.         $existence = new EntityExistence($definition->getEntityName(), [], truefalsefalse, []);
  81.         $params = new WriteParameterBag($definitionWriteContext::createFromContext($context), '', new WriteCommandQueue());
  82.         foreach ($fields as $field) {
  83.             if ($field instanceof VersionField || $field instanceof ReferenceVersionField) {
  84.                 $value $context->getVersionId();
  85.             } else {
  86.                 $value $primaryKey[$field->getPropertyName()];
  87.             }
  88.             $kvPair = new KeyValuePair($field->getPropertyName(), $valuetrue);
  89.             $encoded $field->getSerializer()->encode($field$existence$kvPair$params);
  90.             foreach ($encoded as $key => $value) {
  91.                 $mapped[$key] = $value;
  92.             }
  93.         }
  94.         return $mapped;
  95.     }
  96.     /**
  97.      * Allows simple overwrite for specialized entity hydrators
  98.      */
  99.     protected function assign(EntityDefinition $definitionEntity $entitystring $root, array $rowContext $context): Entity
  100.     {
  101.         $entity $this->hydrateFields($definition$entity$root$row$context$definition->getFields());
  102.         return $entity;
  103.     }
  104.     protected function hydrateFields(EntityDefinition $definitionEntity $entitystring $root, array $rowContext $contextiterable $fields): Entity
  105.     {
  106.         /** @var ArrayStruct $foreignKeys */
  107.         $foreignKeys $entity->getExtension(EntityReader::FOREIGN_KEYS);
  108.         $isPartial self::$partial !== [];
  109.         foreach ($fields as $field) {
  110.             $property $field->getPropertyName();
  111.             if ($isPartial && !isset(self::$partial[$property])) {
  112.                 continue;
  113.             }
  114.             $key $root '.' $property;
  115.             if ($field instanceof ParentAssociationField) {
  116.                 continue;
  117.             }
  118.             if ($field instanceof ManyToManyAssociationField) {
  119.                 $this->manyToMany($row$root$entity$field);
  120.                 continue;
  121.             }
  122.             if ($field instanceof ManyToOneAssociationField || $field instanceof OneToOneAssociationField) {
  123.                 $association $this->manyToOne($row$root$field$context);
  124.                 if ($association === null && $entity instanceof PartialEntity) {
  125.                     continue;
  126.                 }
  127.                 if ($field->is(Extension::class)) {
  128.                     $entity->addExtension($property$association);
  129.                 } else {
  130.                     $entity->assign([$property => $association]);
  131.                 }
  132.                 continue;
  133.             }
  134.             //other association fields are not handled in entity reader query
  135.             if ($field instanceof AssociationField) {
  136.                 continue;
  137.             }
  138.             if (!\array_key_exists($key$row)) {
  139.                 continue;
  140.             }
  141.             $value $row[$key];
  142.             $typed $field;
  143.             if ($field instanceof TranslatedField) {
  144.                 $typed EntityDefinitionQueryHelper::getTranslatedField($definition$field);
  145.             }
  146.             if ($typed instanceof CustomFields) {
  147.                 $this->customFields($definition$row$root$entity$field$context);
  148.                 continue;
  149.             }
  150.             if ($field instanceof TranslatedField) {
  151.                 // contains the resolved translation chain value
  152.                 $decoded $typed->getSerializer()->decode($typed$value);
  153.                 $entity->addTranslated($property$decoded);
  154.                 $inherited $definition->isInheritanceAware() && $context->considerInheritance();
  155.                 $chain EntityDefinitionQueryHelper::buildTranslationChain($root$context$inherited);
  156.                 // assign translated value of the first language
  157.                 $key array_shift($chain) . '.' $property;
  158.                 $decoded $typed->getSerializer()->decode($typed$row[$key]);
  159.                 $entity->assign([$property => $decoded]);
  160.                 continue;
  161.             }
  162.             $decoded $definition->decode($property$value);
  163.             if ($field->is(Extension::class)) {
  164.                 $foreignKeys->set($property$decoded);
  165.             } else {
  166.                 $entity->assign([$property => $decoded]);
  167.             }
  168.         }
  169.         return $entity;
  170.     }
  171.     protected function manyToMany(array $rowstring $rootEntity $entity, ?Field $field): void
  172.     {
  173.         if ($field === null) {
  174.             throw new \RuntimeException('No field provided');
  175.         }
  176.         $accessor $root '.' $field->getPropertyName() . '.id_mapping';
  177.         //many to many isn't loaded in case of limited association criterias
  178.         if (!\array_key_exists($accessor$row)) {
  179.             return;
  180.         }
  181.         //explode hexed ids
  182.         $ids explode('||', (string) $row[$accessor]);
  183.         $ids array_map('strtolower'array_filter($ids));
  184.         /** @var ArrayStruct $mapping */
  185.         $mapping $entity->getExtension(EntityReader::INTERNAL_MAPPING_STORAGE);
  186.         $mapping->set($field->getPropertyName(), $ids);
  187.     }
  188.     protected function translate(EntityDefinition $definitionEntity $entity, array $rowstring $rootContext $context, array $fields): void
  189.     {
  190.         $inherited $definition->isInheritanceAware() && $context->considerInheritance();
  191.         $chain EntityDefinitionQueryHelper::buildTranslationChain($root$context$inherited);
  192.         $translatedFields $this->getTranslatedFields($definition$fields);
  193.         foreach ($translatedFields as $field => $typed) {
  194.             $entity->addTranslated($field$typed->getSerializer()->decode($typedself::value($row$root$field)));
  195.             $entity->$field $typed->getSerializer()->decode($typedself::value($row$chain[0], $field));
  196.         }
  197.     }
  198.     protected function getTranslatedFields(EntityDefinition $definition, array $fields): array
  199.     {
  200.         $key $definition->getEntityName();
  201.         if (isset(self::$translatedFields[$key])) {
  202.             return self::$translatedFields[$key];
  203.         }
  204.         $translatedFields = [];
  205.         /** @var TranslatedField $field */
  206.         foreach ($fields as $field) {
  207.             $translatedFields[$field->getPropertyName()] = EntityDefinitionQueryHelper::getTranslatedField($definition$field);
  208.         }
  209.         return self::$translatedFields[$key] = $translatedFields;
  210.     }
  211.     protected function manyToOne(array $rowstring $root, ?Field $fieldContext $context): ?Entity
  212.     {
  213.         if ($field === null) {
  214.             throw new \RuntimeException('No field provided');
  215.         }
  216.         if (!$field instanceof AssociationField) {
  217.             throw new \RuntimeException(sprintf('Provided field %s is no association field'$field->getPropertyName()));
  218.         }
  219.         $pk $this->getManyToOneProperty($field);
  220.         $association $root '.' $field->getPropertyName();
  221.         $key $association '.' $pk;
  222.         if (!isset($row[$key])) {
  223.             return null;
  224.         }
  225.         return $this->hydrateEntity($field->getReferenceDefinition(), $field->getReferenceDefinition()->getEntityClass(), $row$association$contextself::$partial[$field->getPropertyName()] ?? []);
  226.     }
  227.     protected function customFields(EntityDefinition $definition, array $rowstring $rootEntity $entity, ?Field $fieldContext $context): void
  228.     {
  229.         if ($field === null) {
  230.             return;
  231.         }
  232.         $inherited $field->is(Inherited::class) && $context->considerInheritance();
  233.         $propertyName $field->getPropertyName();
  234.         $value self::value($row$root$propertyName);
  235.         if ($field instanceof TranslatedField) {
  236.             $customField EntityDefinitionQueryHelper::getTranslatedField($definition$field);
  237.             $chain EntityDefinitionQueryHelper::buildTranslationChain($root$context$inherited);
  238.             $decoded $customField->getSerializer()->decode($customFieldself::value($row$chain[0], $propertyName));
  239.             $entity->assign([$propertyName => $decoded]);
  240.             $values = [];
  241.             foreach ($chain as $accessor) {
  242.                 $key $accessor '.' $propertyName;
  243.                 $values[] = $row[$key] ?? null;
  244.             }
  245.             if (empty($values)) {
  246.                 return;
  247.             }
  248.             /**
  249.              * `array_merge`s ordering is reversed compared to the translations array.
  250.              * In other terms: The first argument has the lowest 'priority', so we need to reverse the array
  251.              */
  252.             $merged $this->mergeJson(array_reverse($valuesfalse));
  253.             $decoded $customField->getSerializer()->decode($customField$merged);
  254.             $entity->addTranslated($propertyName$decoded);
  255.             if ($inherited) {
  256.                 $entity->assign([$propertyName => $decoded]);
  257.             }
  258.             return;
  259.         }
  260.         // field is not inherited or request should work with raw data? decode child attributes and return
  261.         if (!$inherited) {
  262.             $value $field->getSerializer()->decode($field$value);
  263.             $entity->assign([$propertyName => $value]);
  264.             return;
  265.         }
  266.         $parentKey $root '.' $propertyName '.inherited';
  267.         // parent has no attributes? decode only child attributes and return
  268.         if (!isset($row[$parentKey])) {
  269.             $value $field->getSerializer()->decode($field$value);
  270.             $entity->assign([$propertyName => $value]);
  271.             return;
  272.         }
  273.         // merge child attributes with parent attributes and assign
  274.         $mergedJson $this->mergeJson([$row[$parentKey], $value]);
  275.         $merged $field->getSerializer()->decode($field$mergedJson);
  276.         $entity->assign([$propertyName => $merged]);
  277.     }
  278.     protected static function value(array $rowstring $rootstring $property): ?string
  279.     {
  280.         $accessor $root '.' $property;
  281.         return $row[$accessor] ?? null;
  282.     }
  283.     protected function getManyToOneProperty(AssociationField $field): string
  284.     {
  285.         $key $field->getReferenceDefinition()->getEntityName() . '.' $field->getReferenceField();
  286.         if (isset(self::$manyToOne[$key])) {
  287.             return self::$manyToOne[$key];
  288.         }
  289.         $reference $field->getReferenceDefinition()->getFields()->getByStorageName(
  290.             $field->getReferenceField()
  291.         );
  292.         if ($reference === null) {
  293.             throw new \RuntimeException(sprintf(
  294.                 'Can not find field by storage name %s in definition %s',
  295.                 $field->getReferenceField(),
  296.                 $field->getReferenceDefinition()->getEntityName()
  297.             ));
  298.         }
  299.         return self::$manyToOne[$key] = $reference->getPropertyName();
  300.     }
  301.     /**
  302.      * @param array<string|null> $jsonStrings
  303.      */
  304.     protected function mergeJson(array $jsonStrings): string
  305.     {
  306.         $merged = [];
  307.         foreach ($jsonStrings as $string) {
  308.             if ($string === null) {
  309.                 continue;
  310.             }
  311.             $decoded json_decode($stringtrue);
  312.             if (!$decoded) {
  313.                 continue;
  314.             }
  315.             foreach ($decoded as $key => $value) {
  316.                 if ($value === null) {
  317.                     continue;
  318.                 }
  319.                 $merged[$key] = $value;
  320.             }
  321.         }
  322.         return json_encode($merged\JSON_PRESERVE_ZERO_FRACTION);
  323.     }
  324.     private function hydrateEntity(EntityDefinition $definitionstring $entityClass, array $rowstring $rootContext $context, array $partial = []): Entity
  325.     {
  326.         $isPartial $partial !== [];
  327.         $hydratorClass $definition->getHydratorClass();
  328.         $entityClass $isPartial PartialEntity::class : $entityClass;
  329.         if ($isPartial) {
  330.             $hydratorClass EntityHydrator::class;
  331.         }
  332.         $hydrator $this->container->get($hydratorClass);
  333.         if (!$hydrator instanceof self) {
  334.             throw new \RuntimeException(sprintf('Hydrator for entity %s not registered'$definition->getEntityName()));
  335.         }
  336.         $identifier implode('-'self::buildUniqueIdentifier($definition$row$root));
  337.         $cacheKey $root '::' $identifier;
  338.         if (isset(self::$hydrated[$cacheKey])) {
  339.             return self::$hydrated[$cacheKey];
  340.         }
  341.         $entity = new $entityClass();
  342.         if (!$entity instanceof Entity) {
  343.             throw new \RuntimeException(sprintf('Expected instance of Entity.php, got %s'\get_class($entity)));
  344.         }
  345.         $entity->addExtension(EntityReader::FOREIGN_KEYS, new ArrayStruct());
  346.         $entity->addExtension(EntityReader::INTERNAL_MAPPING_STORAGE, new ArrayStruct());
  347.         $entity->setUniqueIdentifier($identifier);
  348.         $entity->internalSetEntityData($definition->getEntityName(), $definition->getFieldVisibility());
  349.         $entity $hydrator->assign($definition$entity$root$row$context);
  350.         return self::$hydrated[$cacheKey] = $entity;
  351.     }
  352. }