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

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