vendor/shopware/core/Framework/DataAbstractionLayer/EntityDefinition.php line 363

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\DataAbstractionLayer;
  3. use Shopware\Core\Content\Seo\SeoUrl\SeoUrlDefinition;
  4. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityHydrator;
  5. use Shopware\Core\Framework\DataAbstractionLayer\EntityProtection\EntityProtectionCollection;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Field\AutoIncrementField;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Field\CreatedAtField;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\ApiAware;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Computed;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Extension;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Runtime;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Field\JsonField;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Field\LockedField;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Field\ParentAssociationField;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField;
  23. use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslationsAssociationField;
  25. use Shopware\Core\Framework\DataAbstractionLayer\Field\UpdatedAtField;
  26. use Shopware\Core\Framework\Struct\ArrayEntity;
  27. abstract class EntityDefinition
  28. {
  29.     protected ?CompiledFieldCollection $fields null;
  30.     /**
  31.      * @var EntityExtension[]
  32.      */
  33.     protected array $extensions = [];
  34.     protected ?TranslationsAssociationField $translationField null;
  35.     protected ?CompiledFieldCollection $primaryKeys null;
  36.     protected DefinitionInstanceRegistry $registry;
  37.     /**
  38.      * @var TranslatedField[]
  39.      */
  40.     protected array $translatedFields = [];
  41.     /**
  42.      * @var Field[]
  43.      */
  44.     protected array $extensionFields = [];
  45.     /**
  46.      * @var EntityDefinition|false|null
  47.      */
  48.     private $parentDefinition false;
  49.     private string $className;
  50.     private ?FieldVisibility $fieldVisibility null;
  51.     final public function __construct()
  52.     {
  53.         $this->className = static::class;
  54.     }
  55.     /**
  56.      * @return class-string<EntityDefinition>
  57.      */
  58.     final public function getClass(): string
  59.     {
  60.         return static::class;
  61.     }
  62.     final public function isInstanceOf(EntityDefinition $other): bool
  63.     {
  64.         // same reference or instance of the other class
  65.         return $this === $other
  66.             || ($other->getClass() !== EntityDefinition::class && $this instanceof $other);
  67.     }
  68.     public function compile(DefinitionInstanceRegistry $registry): void
  69.     {
  70.         $this->registry $registry;
  71.     }
  72.     final public function addExtension(EntityExtension $extension): void
  73.     {
  74.         $this->extensions[] = $extension;
  75.         $this->fields null;
  76.     }
  77.     /**
  78.      * @internal
  79.      * Use this only for test purposes
  80.      */
  81.     final public function removeExtension(EntityExtension $toDelete): void
  82.     {
  83.         foreach ($this->extensions as $key => $extension) {
  84.             if (\get_class($extension) === \get_class($toDelete)) {
  85.                 unset($this->extensions[$key]);
  86.                 $this->fields null;
  87.                 return;
  88.             }
  89.         }
  90.     }
  91.     abstract public function getEntityName(): string;
  92.     final public function getFields(): CompiledFieldCollection
  93.     {
  94.         if ($this->fields !== null) {
  95.             return $this->fields;
  96.         }
  97.         $fields $this->defineFields();
  98.         foreach ($this->defaultFields() as $field) {
  99.             $fields->add($field);
  100.         }
  101.         foreach ($this->extensions as $extension) {
  102.             $new = new FieldCollection();
  103.             $extension->extendFields($new);
  104.             foreach ($new as $field) {
  105.                 $field->addFlags(new Extension());
  106.                 if ($field instanceof AssociationField) {
  107.                     $fields->add($field);
  108.                     continue;
  109.                 }
  110.                 if ($field->is(Runtime::class)) {
  111.                     $fields->add($field);
  112.                     continue;
  113.                 }
  114.                 if ($field instanceof ReferenceVersionField) {
  115.                     $fields->add($field);
  116.                     continue;
  117.                 }
  118.                 if (!$field instanceof FkField) {
  119.                     throw new \Exception('Only AssociationFields, FkFields/ReferenceVersionFields for a ManyToOneAssociationField or fields flagged as Runtime can be added as Extension.');
  120.                 }
  121.                 if (!$this->hasAssociationWithStorageName($field->getStorageName(), $new)) {
  122.                     throw new \Exception(sprintf('FkField %s has no configured OneToOneAssociationField or ManyToOneAssociationField in entity %s'$field->getPropertyName(), $this->className));
  123.                 }
  124.                 $fields->add($field);
  125.             }
  126.         }
  127.         foreach ($this->getBaseFields() as $baseField) {
  128.             $fields->add($baseField);
  129.         }
  130.         foreach ($fields as $field) {
  131.             if ($field instanceof TranslationsAssociationField) {
  132.                 $this->translationField $field;
  133.                 $fields->add(
  134.                     (new JsonField('translated''translated'))->addFlags(new ApiAware(), new Computed(), new Runtime())
  135.                 );
  136.                 break;
  137.             }
  138.         }
  139.         $this->fields $fields->compile($this->registry);
  140.         return $this->fields;
  141.     }
  142.     final public function getProtections(): EntityProtectionCollection
  143.     {
  144.         $protections $this->defineProtections();
  145.         foreach ($this->extensions as $extension) {
  146.             if (!$extension instanceof EntityExtension) {
  147.                 continue;
  148.             }
  149.             $extension->extendProtections($protections);
  150.         }
  151.         return $protections;
  152.     }
  153.     final public function getField(string $propertyName): ?Field
  154.     {
  155.         return $this->getFields()->get($propertyName);
  156.     }
  157.     final public function getFieldVisibility(): FieldVisibility
  158.     {
  159.         if ($this->fieldVisibility) {
  160.             return $this->fieldVisibility;
  161.         }
  162.         /** @var array<string> $internalProperties */
  163.         $internalProperties $this->getFields()
  164.             ->fmap(function (Field $field): ?string {
  165.                 if ($field->is(ApiAware::class)) {
  166.                     return null;
  167.                 }
  168.                 return $field->getPropertyName();
  169.             });
  170.         return $this->fieldVisibility = new FieldVisibility(array_values($internalProperties));
  171.     }
  172.     /**
  173.      * Phpstan will complain that we should specify the generic type if we hint that class strings
  174.      * of EntityColllection should be returned.
  175.      *
  176.      * @return class-string
  177.      */
  178.     public function getCollectionClass(): string
  179.     {
  180.         return EntityCollection::class;
  181.     }
  182.     /**
  183.      * @return class-string<Entity>
  184.      */
  185.     public function getEntityClass(): string
  186.     {
  187.         return ArrayEntity::class;
  188.     }
  189.     public function getParentDefinition(): ?EntityDefinition
  190.     {
  191.         if ($this->parentDefinition !== false) {
  192.             return $this->parentDefinition;
  193.         }
  194.         $parentDefinitionClass $this->getParentDefinitionClass();
  195.         if ($parentDefinitionClass === null) {
  196.             return $this->parentDefinition null;
  197.         }
  198.         $this->parentDefinition $this->registry->getByClassOrEntityName($parentDefinitionClass);
  199.         return $this->parentDefinition;
  200.     }
  201.     final public function getTranslationDefinition(): ?EntityDefinition
  202.     {
  203.         // value is initialized from this method
  204.         $this->getFields();
  205.         if ($this->translationField === null) {
  206.             return null;
  207.         }
  208.         return $this->translationField->getReferenceDefinition();
  209.     }
  210.     final public function getTranslationField(): ?TranslationsAssociationField
  211.     {
  212.         // value is initialized from this method
  213.         $this->getFields();
  214.         return $this->translationField;
  215.     }
  216.     final public function hasAutoIncrement(): bool
  217.     {
  218.         return $this->getField('autoIncrement') instanceof AutoIncrementField;
  219.     }
  220.     final public function getPrimaryKeys(): CompiledFieldCollection
  221.     {
  222.         if ($this->primaryKeys !== null) {
  223.             return $this->primaryKeys;
  224.         }
  225.         $fields $this->getFields()->filter(function (Field $field): bool {
  226.             return $field->is(PrimaryKey::class);
  227.         });
  228.         $fields->sort(static function (Field $aField $b) {
  229.             return $b->getExtractPriority() <=> $a->getExtractPriority();
  230.         });
  231.         return $this->primaryKeys $fields;
  232.     }
  233.     /**
  234.      * @return array<mixed>
  235.      */
  236.     public function getDefaults(): array
  237.     {
  238.         return [];
  239.     }
  240.     public function getChildDefaults(): array
  241.     {
  242.         return [];
  243.     }
  244.     public function isChildrenAware(): bool
  245.     {
  246.         //used in VersionManager
  247.         return $this->getFields()->getChildrenAssociationField() !== null;
  248.     }
  249.     public function isParentAware(): bool
  250.     {
  251.         return $this->getFields()->get('parent') instanceof ParentAssociationField;
  252.     }
  253.     public function isInheritanceAware(): bool
  254.     {
  255.         return false;
  256.     }
  257.     public function isVersionAware(): bool
  258.     {
  259.         return $this->getFields()->has('versionId');
  260.     }
  261.     public function isLockAware(): bool
  262.     {
  263.         $field $this->getFields()->get('locked');
  264.         return $field && $field instanceof LockedField;
  265.     }
  266.     public function isSeoAware(): bool
  267.     {
  268.         $field $this->getFields()->get('seoUrls');
  269.         return $field instanceof OneToManyAssociationField && $field->getReferenceDefinition() instanceof SeoUrlDefinition;
  270.     }
  271.     public function since(): ?string
  272.     {
  273.         return null;
  274.     }
  275.     public function getHydratorClass(): string
  276.     {
  277.         return EntityHydrator::class;
  278.     }
  279.     /**
  280.      * @internal
  281.      *
  282.      * @return mixed
  283.      */
  284.     public function decode(string $property, ?string $value)
  285.     {
  286.         $field $this->getField($property);
  287.         if ($field === null) {
  288.             throw new \RuntimeException(sprintf('Field %s not found'$property));
  289.         }
  290.         return $field->getSerializer()->decode($field$value);
  291.     }
  292.     public function getTranslatedFields(): array
  293.     {
  294.         return $this->getFields()->getTranslatedFields();
  295.     }
  296.     public function getExtensionFields(): array
  297.     {
  298.         return $this->getFields()->getExtensionFields();
  299.     }
  300.     protected function getParentDefinitionClass(): ?string
  301.     {
  302.         return null;
  303.     }
  304.     /**
  305.      * @return Field[]
  306.      */
  307.     protected function defaultFields(): array
  308.     {
  309.         return [
  310.             (new CreatedAtField())->addFlags(new ApiAware()),
  311.             (new UpdatedAtField())->addFlags(new ApiAware()),
  312.         ];
  313.     }
  314.     abstract protected function defineFields(): FieldCollection;
  315.     protected function defineProtections(): EntityProtectionCollection
  316.     {
  317.         return new EntityProtectionCollection();
  318.     }
  319.     protected function getBaseFields(): array
  320.     {
  321.         return [];
  322.     }
  323.     private function hasAssociationWithStorageName(string $storageNameFieldCollection $new): bool
  324.     {
  325.         foreach ($new as $association) {
  326.             if (!$association instanceof ManyToOneAssociationField && !$association instanceof OneToOneAssociationField) {
  327.                 continue;
  328.             }
  329.             if ($association->getStorageName() === $storageName) {
  330.                 return true;
  331.             }
  332.         }
  333.         return false;
  334.     }
  335. }