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

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