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

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