vendor/shopware/core/Framework/DataAbstractionLayer/Dbal/EntityHydrator.php line 479

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