From bf6655a534a6775d30cafa67bd801276bda1d98d Mon Sep 17 00:00:00 2001 From: polo Date: Tue, 13 Aug 2024 23:45:21 +0200 Subject: =?UTF-8?q?VERSION=200.2=20doctrine=20ORM=20et=20entit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doctrine/orm/src/Mapping/AnsiQuoteStrategy.php | 76 + .../orm/src/Mapping/ArrayAccessImplementation.php | 70 + .../orm/src/Mapping/AssociationMapping.php | 359 +++ .../orm/src/Mapping/AssociationOverride.php | 51 + .../orm/src/Mapping/AssociationOverrides.php | 38 + .../doctrine/orm/src/Mapping/AttributeOverride.php | 15 + .../orm/src/Mapping/AttributeOverrides.php | 38 + .../orm/src/Mapping/Builder/AssociationBuilder.php | 171 ++ .../src/Mapping/Builder/ClassMetadataBuilder.php | 426 ++++ .../orm/src/Mapping/Builder/EmbeddedBuilder.php | 46 + .../src/Mapping/Builder/EntityListenerBuilder.php | 55 + .../orm/src/Mapping/Builder/FieldBuilder.php | 243 ++ .../Builder/ManyToManyAssociationBuilder.php | 73 + .../Builder/OneToManyAssociationBuilder.php | 46 + vendor/doctrine/orm/src/Mapping/Cache.php | 19 + .../orm/src/Mapping/ChainTypedFieldMapper.php | 35 + .../orm/src/Mapping/ChangeTrackingPolicy.php | 17 + vendor/doctrine/orm/src/Mapping/ClassMetadata.php | 2649 ++++++++++++++++++++ .../orm/src/Mapping/ClassMetadataFactory.php | 729 ++++++ vendor/doctrine/orm/src/Mapping/Column.php | 36 + .../doctrine/orm/src/Mapping/CustomIdGenerator.php | 16 + .../src/Mapping/DefaultEntityListenerResolver.php | 40 + .../orm/src/Mapping/DefaultNamingStrategy.php | 68 + .../orm/src/Mapping/DefaultQuoteStrategy.php | 145 ++ .../orm/src/Mapping/DefaultTypedFieldMapper.php | 80 + .../orm/src/Mapping/DiscriminatorColumn.php | 24 + .../orm/src/Mapping/DiscriminatorColumnMapping.php | 83 + .../doctrine/orm/src/Mapping/DiscriminatorMap.php | 17 + .../orm/src/Mapping/Driver/AttributeDriver.php | 768 ++++++ .../orm/src/Mapping/Driver/AttributeReader.php | 146 ++ .../orm/src/Mapping/Driver/DatabaseDriver.php | 528 ++++ .../src/Mapping/Driver/ReflectionBasedDriver.php | 44 + .../Driver/RepeatableAttributeCollection.php | 16 + .../orm/src/Mapping/Driver/SimplifiedXmlDriver.php | 25 + .../doctrine/orm/src/Mapping/Driver/XmlDriver.php | 940 +++++++ vendor/doctrine/orm/src/Mapping/Embeddable.php | 12 + vendor/doctrine/orm/src/Mapping/Embedded.php | 17 + .../orm/src/Mapping/EmbeddedClassMapping.php | 93 + vendor/doctrine/orm/src/Mapping/Entity.php | 20 + .../orm/src/Mapping/EntityListenerResolver.php | 30 + .../doctrine/orm/src/Mapping/EntityListeners.php | 21 + .../Mapping/Exception/InvalidCustomGenerator.php | 28 + .../src/Mapping/Exception/UnknownGeneratorType.php | 16 + vendor/doctrine/orm/src/Mapping/FieldMapping.php | 169 ++ vendor/doctrine/orm/src/Mapping/GeneratedValue.php | 17 + .../orm/src/Mapping/HasLifecycleCallbacks.php | 12 + vendor/doctrine/orm/src/Mapping/Id.php | 12 + vendor/doctrine/orm/src/Mapping/Index.php | 26 + .../doctrine/orm/src/Mapping/InheritanceType.php | 17 + .../doctrine/orm/src/Mapping/InverseJoinColumn.php | 13 + .../orm/src/Mapping/InverseSideMapping.php | 30 + vendor/doctrine/orm/src/Mapping/JoinColumn.php | 13 + .../doctrine/orm/src/Mapping/JoinColumnMapping.php | 77 + .../orm/src/Mapping/JoinColumnProperties.php | 21 + vendor/doctrine/orm/src/Mapping/JoinColumns.php | 14 + vendor/doctrine/orm/src/Mapping/JoinTable.php | 35 + .../doctrine/orm/src/Mapping/JoinTableMapping.php | 115 + vendor/doctrine/orm/src/Mapping/ManyToMany.php | 27 + .../src/Mapping/ManyToManyAssociationMapping.php | 9 + .../src/Mapping/ManyToManyInverseSideMapping.php | 9 + .../src/Mapping/ManyToManyOwningSideMapping.php | 185 ++ vendor/doctrine/orm/src/Mapping/ManyToOne.php | 24 + .../src/Mapping/ManyToOneAssociationMapping.php | 12 + .../doctrine/orm/src/Mapping/MappedSuperclass.php | 18 + .../doctrine/orm/src/Mapping/MappingAttribute.php | 10 + .../doctrine/orm/src/Mapping/MappingException.php | 691 +++++ vendor/doctrine/orm/src/Mapping/NamingStrategy.php | 71 + vendor/doctrine/orm/src/Mapping/OneToMany.php | 26 + .../src/Mapping/OneToManyAssociationMapping.php | 75 + vendor/doctrine/orm/src/Mapping/OneToOne.php | 26 + .../orm/src/Mapping/OneToOneAssociationMapping.php | 9 + .../orm/src/Mapping/OneToOneInverseSideMapping.php | 9 + .../orm/src/Mapping/OneToOneOwningSideMapping.php | 9 + vendor/doctrine/orm/src/Mapping/OrderBy.php | 17 + .../doctrine/orm/src/Mapping/OwningSideMapping.php | 28 + vendor/doctrine/orm/src/Mapping/PostLoad.php | 12 + vendor/doctrine/orm/src/Mapping/PostPersist.php | 12 + vendor/doctrine/orm/src/Mapping/PostRemove.php | 12 + vendor/doctrine/orm/src/Mapping/PostUpdate.php | 12 + vendor/doctrine/orm/src/Mapping/PreFlush.php | 12 + vendor/doctrine/orm/src/Mapping/PrePersist.php | 12 + vendor/doctrine/orm/src/Mapping/PreRemove.php | 12 + vendor/doctrine/orm/src/Mapping/PreUpdate.php | 12 + vendor/doctrine/orm/src/Mapping/QuoteStrategy.php | 68 + .../orm/src/Mapping/ReflectionEmbeddedProperty.php | 61 + .../orm/src/Mapping/ReflectionEnumProperty.php | 87 + .../orm/src/Mapping/ReflectionReadonlyProperty.php | 49 + .../doctrine/orm/src/Mapping/SequenceGenerator.php | 18 + vendor/doctrine/orm/src/Mapping/Table.php | 45 + .../orm/src/Mapping/ToManyAssociationMapping.php | 16 + .../ToManyAssociationMappingImplementation.php | 69 + .../orm/src/Mapping/ToManyInverseSideMapping.php | 10 + .../orm/src/Mapping/ToManyOwningSideMapping.php | 10 + .../orm/src/Mapping/ToOneAssociationMapping.php | 9 + .../orm/src/Mapping/ToOneInverseSideMapping.php | 52 + .../orm/src/Mapping/ToOneOwningSideMapping.php | 212 ++ .../doctrine/orm/src/Mapping/TypedFieldMapper.php | 20 + .../orm/src/Mapping/UnderscoreNamingStrategy.php | 108 + .../doctrine/orm/src/Mapping/UniqueConstraint.php | 24 + vendor/doctrine/orm/src/Mapping/Version.php | 12 + 100 files changed, 11161 insertions(+) create mode 100644 vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php create mode 100644 vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php create mode 100644 vendor/doctrine/orm/src/Mapping/AssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/AssociationOverride.php create mode 100644 vendor/doctrine/orm/src/Mapping/AssociationOverrides.php create mode 100644 vendor/doctrine/orm/src/Mapping/AttributeOverride.php create mode 100644 vendor/doctrine/orm/src/Mapping/AttributeOverrides.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php create mode 100644 vendor/doctrine/orm/src/Mapping/Cache.php create mode 100644 vendor/doctrine/orm/src/Mapping/ChainTypedFieldMapper.php create mode 100644 vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php create mode 100644 vendor/doctrine/orm/src/Mapping/ClassMetadata.php create mode 100644 vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php create mode 100644 vendor/doctrine/orm/src/Mapping/Column.php create mode 100644 vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php create mode 100644 vendor/doctrine/orm/src/Mapping/DefaultEntityListenerResolver.php create mode 100644 vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php create mode 100644 vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php create mode 100644 vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php create mode 100644 vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php create mode 100644 vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php create mode 100644 vendor/doctrine/orm/src/Mapping/Driver/XmlDriver.php create mode 100644 vendor/doctrine/orm/src/Mapping/Embeddable.php create mode 100644 vendor/doctrine/orm/src/Mapping/Embedded.php create mode 100644 vendor/doctrine/orm/src/Mapping/EmbeddedClassMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/Entity.php create mode 100644 vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php create mode 100644 vendor/doctrine/orm/src/Mapping/EntityListeners.php create mode 100644 vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php create mode 100644 vendor/doctrine/orm/src/Mapping/Exception/UnknownGeneratorType.php create mode 100644 vendor/doctrine/orm/src/Mapping/FieldMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/GeneratedValue.php create mode 100644 vendor/doctrine/orm/src/Mapping/HasLifecycleCallbacks.php create mode 100644 vendor/doctrine/orm/src/Mapping/Id.php create mode 100644 vendor/doctrine/orm/src/Mapping/Index.php create mode 100644 vendor/doctrine/orm/src/Mapping/InheritanceType.php create mode 100644 vendor/doctrine/orm/src/Mapping/InverseJoinColumn.php create mode 100644 vendor/doctrine/orm/src/Mapping/InverseSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/JoinColumn.php create mode 100644 vendor/doctrine/orm/src/Mapping/JoinColumnMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php create mode 100644 vendor/doctrine/orm/src/Mapping/JoinColumns.php create mode 100644 vendor/doctrine/orm/src/Mapping/JoinTable.php create mode 100644 vendor/doctrine/orm/src/Mapping/JoinTableMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ManyToMany.php create mode 100644 vendor/doctrine/orm/src/Mapping/ManyToManyAssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ManyToManyInverseSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ManyToManyOwningSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ManyToOne.php create mode 100644 vendor/doctrine/orm/src/Mapping/ManyToOneAssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/MappedSuperclass.php create mode 100644 vendor/doctrine/orm/src/Mapping/MappingAttribute.php create mode 100644 vendor/doctrine/orm/src/Mapping/MappingException.php create mode 100644 vendor/doctrine/orm/src/Mapping/NamingStrategy.php create mode 100644 vendor/doctrine/orm/src/Mapping/OneToMany.php create mode 100644 vendor/doctrine/orm/src/Mapping/OneToManyAssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/OneToOne.php create mode 100644 vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/OneToOneInverseSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/OneToOneOwningSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/OrderBy.php create mode 100644 vendor/doctrine/orm/src/Mapping/OwningSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/PostLoad.php create mode 100644 vendor/doctrine/orm/src/Mapping/PostPersist.php create mode 100644 vendor/doctrine/orm/src/Mapping/PostRemove.php create mode 100644 vendor/doctrine/orm/src/Mapping/PostUpdate.php create mode 100644 vendor/doctrine/orm/src/Mapping/PreFlush.php create mode 100644 vendor/doctrine/orm/src/Mapping/PrePersist.php create mode 100644 vendor/doctrine/orm/src/Mapping/PreRemove.php create mode 100644 vendor/doctrine/orm/src/Mapping/PreUpdate.php create mode 100644 vendor/doctrine/orm/src/Mapping/QuoteStrategy.php create mode 100644 vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php create mode 100644 vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php create mode 100644 vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php create mode 100644 vendor/doctrine/orm/src/Mapping/SequenceGenerator.php create mode 100644 vendor/doctrine/orm/src/Mapping/Table.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToManyOwningSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToOneAssociationMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToOneInverseSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php create mode 100644 vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php create mode 100644 vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php create mode 100644 vendor/doctrine/orm/src/Mapping/UniqueConstraint.php create mode 100644 vendor/doctrine/orm/src/Mapping/Version.php (limited to 'vendor/doctrine/orm/src/Mapping') diff --git a/vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php b/vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php new file mode 100644 index 0000000..872d4d6 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AnsiQuoteStrategy.php @@ -0,0 +1,76 @@ +fieldMappings[$fieldName]->columnName; + } + + public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string + { + return $class->table['name']; + } + + /** + * {@inheritDoc} + */ + public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string + { + return $definition['sequenceName']; + } + + public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string + { + return $joinColumn->name; + } + + public function getReferencedJoinColumnName( + JoinColumnMapping $joinColumn, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + return $joinColumn->referencedColumnName; + } + + public function getJoinTableName( + ManyToManyOwningSideMapping $association, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + return $association->joinTable->name; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array + { + return $class->identifier; + } + + public function getColumnAlias( + string $columnName, + int $counter, + AbstractPlatform $platform, + ClassMetadata|null $class = null, + ): string { + return $this->getSQLResultCasing($platform, $columnName . '_' . $counter); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php b/vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php new file mode 100644 index 0000000..3fd0988 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ArrayAccessImplementation.php @@ -0,0 +1,70 @@ +$offset); + } + + /** @param string $offset */ + public function offsetGet(mixed $offset): mixed + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11211', + 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', + static::class, + ); + + if (! property_exists($this, $offset)) { + throw new InvalidArgumentException('Undefined property: ' . $offset); + } + + return $this->$offset; + } + + /** @param string $offset */ + public function offsetSet(mixed $offset, mixed $value): void + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11211', + 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', + static::class, + ); + + $this->$offset = $value; + } + + /** @param string $offset */ + public function offsetUnset(mixed $offset): void + { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11211', + 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', + static::class, + ); + + $this->$offset = null; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AssociationMapping.php b/vendor/doctrine/orm/src/Mapping/AssociationMapping.php new file mode 100644 index 0000000..ce7bdb4 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AssociationMapping.php @@ -0,0 +1,359 @@ + */ +abstract class AssociationMapping implements ArrayAccess +{ + /** + * The names of persistence operations to cascade on the association. + * + * @var list<'persist'|'remove'|'detach'|'refresh'|'all'> + */ + public array $cascade = []; + + /** + * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. + * + * @var ClassMetadata::FETCH_*|null + */ + public int|null $fetch = null; + + /** + * This is set when the association is inherited by this class from another + * (inheritance) parent entity class. The value is the FQCN of the + * topmost entity class that contains this association. (If there are + * transient classes in the class hierarchy, these are ignored, so the + * class property may in fact come from a class further up in the PHP class + * hierarchy.) To-many associations initially declared in mapped + * superclasses are not considered 'inherited' in the nearest + * entity subclasses. + * + * @var class-string|null + */ + public string|null $inherited = null; + + /** + * This is set when the association does not appear in the current class + * for the first time, but is initially declared in another parent + * entity or mapped superclass. The value is the FQCN of the + * topmost non-transient class that contains association information for + * this relationship. + * + * @var class-string|null + */ + public string|null $declared = null; + + public array|null $cache = null; + + public bool|null $id = null; + + public bool|null $isOnDeleteCascade = null; + + /** @var class-string|null */ + public string|null $originalClass = null; + + public string|null $originalField = null; + + public bool $orphanRemoval = false; + + public bool|null $unique = null; + + /** + * @param string $fieldName The name of the field in the entity + * the association is mapped to. + * @param class-string $sourceEntity The class name of the source entity. + * In the case of to-many associations + * initially present in mapped + * superclasses, the nearest + * entity subclasses will be + * considered the respective source + * entities. + * @param class-string $targetEntity The class name of the target entity. + * If it is fully-qualified it is used as + * is. If it is a simple, unqualified + * class name the namespace is assumed to + * be the same as the namespace of the + * source entity. + */ + final public function __construct( + public readonly string $fieldName, + public string $sourceEntity, + public readonly string $targetEntity, + ) { + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): static + { + unset($mappingArray['isOwningSide'], $mappingArray['type']); + $mapping = new static( + $mappingArray['fieldName'], + $mappingArray['sourceEntity'], + $mappingArray['targetEntity'], + ); + unset($mappingArray['fieldName'], $mappingArray['sourceEntity'], $mappingArray['targetEntity']); + + foreach ($mappingArray as $key => $value) { + if ($key === 'joinTable') { + assert($mapping instanceof ManyToManyAssociationMapping); + + if ($value === [] || $value === null) { + continue; + } + + assert($mapping instanceof ManyToManyOwningSideMapping); + + $mapping->joinTable = JoinTableMapping::fromMappingArray($value); + + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value; + } else { + throw new OutOfRangeException('Unknown property ' . $key . ' on class ' . static::class); + } + } + + return $mapping; + } + + /** + * @psalm-assert-if-true OwningSideMapping $this + * @psalm-assert-if-false InverseSideMapping $this + */ + final public function isOwningSide(): bool + { + return $this instanceof OwningSideMapping; + } + + /** @psalm-assert-if-true ToOneAssociationMapping $this */ + final public function isToOne(): bool + { + return $this instanceof ToOneAssociationMapping; + } + + /** @psalm-assert-if-true ToManyAssociationMapping $this */ + final public function isToMany(): bool + { + return $this instanceof ToManyAssociationMapping; + } + + /** @psalm-assert-if-true OneToOneOwningSideMapping $this */ + final public function isOneToOneOwningSide(): bool + { + return $this->isOneToOne() && $this->isOwningSide(); + } + + /** @psalm-assert-if-true OneToOneOwningSideMapping|ManyToOneAssociationMapping $this */ + final public function isToOneOwningSide(): bool + { + return $this->isToOne() && $this->isOwningSide(); + } + + /** @psalm-assert-if-true ManyToManyOwningSideMapping $this */ + final public function isManyToManyOwningSide(): bool + { + return $this instanceof ManyToManyOwningSideMapping; + } + + /** @psalm-assert-if-true OneToOneAssociationMapping $this */ + final public function isOneToOne(): bool + { + return $this instanceof OneToOneAssociationMapping; + } + + /** @psalm-assert-if-true OneToManyAssociationMapping $this */ + final public function isOneToMany(): bool + { + return $this instanceof OneToManyAssociationMapping; + } + + /** @psalm-assert-if-true ManyToOneAssociationMapping $this */ + final public function isManyToOne(): bool + { + return $this instanceof ManyToOneAssociationMapping; + } + + /** @psalm-assert-if-true ManyToManyAssociationMapping $this */ + final public function isManyToMany(): bool + { + return $this instanceof ManyToManyAssociationMapping; + } + + /** @psalm-assert-if-true ToManyAssociationMapping $this */ + final public function isOrdered(): bool + { + return $this->isToMany() && $this->orderBy() !== []; + } + + /** @psalm-assert-if-true ToManyAssociationMapping $this */ + public function isIndexed(): bool + { + return false; + } + + final public function type(): int + { + return match (true) { + $this instanceof OneToOneAssociationMapping => ClassMetadata::ONE_TO_ONE, + $this instanceof OneToManyAssociationMapping => ClassMetadata::ONE_TO_MANY, + $this instanceof ManyToOneAssociationMapping => ClassMetadata::MANY_TO_ONE, + $this instanceof ManyToManyAssociationMapping => ClassMetadata::MANY_TO_MANY, + default => throw new Exception('Cannot determine type for ' . static::class), + }; + } + + /** @param string $offset */ + public function offsetExists(mixed $offset): bool + { + return isset($this->$offset) || in_array($offset, ['isOwningSide', 'type'], true); + } + + final public function offsetGet(mixed $offset): mixed + { + return match ($offset) { + 'isOwningSide' => $this->isOwningSide(), + 'type' => $this->type(), + 'isCascadeRemove' => $this->isCascadeRemove(), + 'isCascadePersist' => $this->isCascadePersist(), + 'isCascadeRefresh' => $this->isCascadeRefresh(), + 'isCascadeDetach' => $this->isCascadeDetach(), + default => property_exists($this, $offset) ? $this->$offset : throw new OutOfRangeException(sprintf( + 'Unknown property "%s" on class %s', + $offset, + static::class, + )), + }; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + assert($offset !== null); + if (! property_exists($this, $offset)) { + throw new OutOfRangeException(sprintf( + 'Unknown property "%s" on class %s', + $offset, + static::class, + )); + } + + if ($offset === 'joinTable') { + $value = JoinTableMapping::fromMappingArray($value); + } + + $this->$offset = $value; + } + + /** @param string $offset */ + public function offsetUnset(mixed $offset): void + { + if (! property_exists($this, $offset)) { + throw new OutOfRangeException(sprintf( + 'Unknown property "%s" on class %s', + $offset, + static::class, + )); + } + + $this->$offset = null; + } + + final public function isCascadeRemove(): bool + { + return in_array('remove', $this->cascade, true); + } + + final public function isCascadePersist(): bool + { + return in_array('persist', $this->cascade, true); + } + + final public function isCascadeRefresh(): bool + { + return in_array('refresh', $this->cascade, true); + } + + final public function isCascadeDetach(): bool + { + return in_array('detach', $this->cascade, true); + } + + /** @return array */ + public function toArray(): array + { + $array = (array) $this; + + $array['isOwningSide'] = $this->isOwningSide(); + $array['type'] = $this->type(); + + return $array; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['fieldName', 'sourceEntity', 'targetEntity']; + + if (count($this->cascade) > 0) { + $serialized[] = 'cascade'; + } + + foreach ( + [ + 'fetch', + 'inherited', + 'declared', + 'cache', + 'originalClass', + 'originalField', + ] as $stringOrArrayProperty + ) { + if ($this->$stringOrArrayProperty !== null) { + $serialized[] = $stringOrArrayProperty; + } + } + + foreach (['id', 'orphanRemoval', 'isOnDeleteCascade', 'unique'] as $boolProperty) { + if ($this->$boolProperty) { + $serialized[] = $boolProperty; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AssociationOverride.php b/vendor/doctrine/orm/src/Mapping/AssociationOverride.php new file mode 100644 index 0000000..e0ebc07 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AssociationOverride.php @@ -0,0 +1,51 @@ +|null + */ + public readonly array|null $joinColumns; + + /** + * The join column that is being mapped to the persistent attribute. + * + * @var array|null + */ + public readonly array|null $inverseJoinColumns; + + /** + * @param string $name The name of the relationship property whose mapping is being overridden. + * @param JoinColumn|array $joinColumns + * @param JoinColumn|array $inverseJoinColumns + * @param JoinTable|null $joinTable The join table that maps the relationship. + * @param string|null $inversedBy The name of the association-field on the inverse-side. + * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch + */ + public function __construct( + public readonly string $name, + array|JoinColumn|null $joinColumns = null, + array|JoinColumn|null $inverseJoinColumns = null, + public readonly JoinTable|null $joinTable = null, + public readonly string|null $inversedBy = null, + public readonly string|null $fetch = null, + ) { + if ($joinColumns instanceof JoinColumn) { + $joinColumns = [$joinColumns]; + } + + if ($inverseJoinColumns instanceof JoinColumn) { + $inverseJoinColumns = [$inverseJoinColumns]; + } + + $this->joinColumns = $joinColumns; + $this->inverseJoinColumns = $inverseJoinColumns; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AssociationOverrides.php b/vendor/doctrine/orm/src/Mapping/AssociationOverrides.php new file mode 100644 index 0000000..9fc6807 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AssociationOverrides.php @@ -0,0 +1,38 @@ + + */ + public readonly array $overrides; + + /** @param array|AssociationOverride $overrides */ + public function __construct(array|AssociationOverride $overrides) + { + if (! is_array($overrides)) { + $overrides = [$overrides]; + } + + foreach ($overrides as $override) { + if (! ($override instanceof AssociationOverride)) { + throw MappingException::invalidOverrideType('AssociationOverride', $override); + } + } + + $this->overrides = array_values($overrides); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/AttributeOverride.php b/vendor/doctrine/orm/src/Mapping/AttributeOverride.php new file mode 100644 index 0000000..8f0e70c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AttributeOverride.php @@ -0,0 +1,15 @@ + + */ + public readonly array $overrides; + + /** @param array|AttributeOverride $overrides */ + public function __construct(array|AttributeOverride $overrides) + { + if (! is_array($overrides)) { + $overrides = [$overrides]; + } + + foreach ($overrides as $override) { + if (! ($override instanceof AttributeOverride)) { + throw MappingException::invalidOverrideType('AttributeOverride', $override); + } + } + + $this->overrides = array_values($overrides); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php new file mode 100644 index 0000000..ea9e13c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/AssociationBuilder.php @@ -0,0 +1,171 @@ +mapping['mappedBy'] = $fieldName; + + return $this; + } + + /** @return $this */ + public function inversedBy(string $fieldName): static + { + $this->mapping['inversedBy'] = $fieldName; + + return $this; + } + + /** @return $this */ + public function cascadeAll(): static + { + $this->mapping['cascade'] = ['ALL']; + + return $this; + } + + /** @return $this */ + public function cascadePersist(): static + { + $this->mapping['cascade'][] = 'persist'; + + return $this; + } + + /** @return $this */ + public function cascadeRemove(): static + { + $this->mapping['cascade'][] = 'remove'; + + return $this; + } + + /** @return $this */ + public function cascadeDetach(): static + { + $this->mapping['cascade'][] = 'detach'; + + return $this; + } + + /** @return $this */ + public function cascadeRefresh(): static + { + $this->mapping['cascade'][] = 'refresh'; + + return $this; + } + + /** @return $this */ + public function fetchExtraLazy(): static + { + $this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; + + return $this; + } + + /** @return $this */ + public function fetchEager(): static + { + $this->mapping['fetch'] = ClassMetadata::FETCH_EAGER; + + return $this; + } + + /** @return $this */ + public function fetchLazy(): static + { + $this->mapping['fetch'] = ClassMetadata::FETCH_LAZY; + + return $this; + } + + /** + * Add Join Columns. + * + * @return $this + */ + public function addJoinColumn( + string $columnName, + string $referencedColumnName, + bool $nullable = true, + bool $unique = false, + string|null $onDelete = null, + string|null $columnDef = null, + ): static { + $this->joinColumns[] = [ + 'name' => $columnName, + 'referencedColumnName' => $referencedColumnName, + 'nullable' => $nullable, + 'unique' => $unique, + 'onDelete' => $onDelete, + 'columnDefinition' => $columnDef, + ]; + + return $this; + } + + /** + * Sets field as primary key. + * + * @return $this + */ + public function makePrimaryKey(): static + { + $this->mapping['id'] = true; + + return $this; + } + + /** + * Removes orphan entities when detached from their parent. + * + * @return $this + */ + public function orphanRemoval(): static + { + $this->mapping['orphanRemoval'] = true; + + return $this; + } + + /** @throws InvalidArgumentException */ + public function build(): ClassMetadataBuilder + { + $mapping = $this->mapping; + if ($this->joinColumns) { + $mapping['joinColumns'] = $this->joinColumns; + } + + $cm = $this->builder->getClassMetadata(); + if ($this->type === ClassMetadata::MANY_TO_ONE) { + $cm->mapManyToOne($mapping); + } elseif ($this->type === ClassMetadata::ONE_TO_ONE) { + $cm->mapOneToOne($mapping); + } else { + throw new InvalidArgumentException('Type should be a ToOne Association here'); + } + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php new file mode 100644 index 0000000..b9d3cc8 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/ClassMetadataBuilder.php @@ -0,0 +1,426 @@ +cm; + } + + /** + * Marks the class as mapped superclass. + * + * @return $this + */ + public function setMappedSuperClass(): static + { + $this->cm->isMappedSuperclass = true; + $this->cm->isEmbeddedClass = false; + + return $this; + } + + /** + * Marks the class as embeddable. + * + * @return $this + */ + public function setEmbeddable(): static + { + $this->cm->isEmbeddedClass = true; + $this->cm->isMappedSuperclass = false; + + return $this; + } + + /** + * Adds and embedded class + * + * @param class-string $class + * + * @return $this + */ + public function addEmbedded(string $fieldName, string $class, string|false|null $columnPrefix = null): static + { + $this->cm->mapEmbedded( + [ + 'fieldName' => $fieldName, + 'class' => $class, + 'columnPrefix' => $columnPrefix, + ], + ); + + return $this; + } + + /** + * Sets custom Repository class name. + * + * @return $this + */ + public function setCustomRepositoryClass(string $repositoryClassName): static + { + $this->cm->setCustomRepositoryClass($repositoryClassName); + + return $this; + } + + /** + * Marks class read only. + * + * @return $this + */ + public function setReadOnly(): static + { + $this->cm->markReadOnly(); + + return $this; + } + + /** + * Sets the table name. + * + * @return $this + */ + public function setTable(string $name): static + { + $this->cm->setPrimaryTable(['name' => $name]); + + return $this; + } + + /** + * Adds Index. + * + * @psalm-param list $columns + * + * @return $this + */ + public function addIndex(array $columns, string $name): static + { + if (! isset($this->cm->table['indexes'])) { + $this->cm->table['indexes'] = []; + } + + $this->cm->table['indexes'][$name] = ['columns' => $columns]; + + return $this; + } + + /** + * Adds Unique Constraint. + * + * @psalm-param list $columns + * + * @return $this + */ + public function addUniqueConstraint(array $columns, string $name): static + { + if (! isset($this->cm->table['uniqueConstraints'])) { + $this->cm->table['uniqueConstraints'] = []; + } + + $this->cm->table['uniqueConstraints'][$name] = ['columns' => $columns]; + + return $this; + } + + /** + * Sets class as root of a joined table inheritance hierarchy. + * + * @return $this + */ + public function setJoinedTableInheritance(): static + { + $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED); + + return $this; + } + + /** + * Sets class as root of a single table inheritance hierarchy. + * + * @return $this + */ + public function setSingleTableInheritance(): static + { + $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); + + return $this; + } + + /** + * Sets the discriminator column details. + * + * @psalm-param class-string|null $enumType + * @psalm-param array $options + * + * @return $this + */ + public function setDiscriminatorColumn( + string $name, + string $type = 'string', + int $length = 255, + string|null $columnDefinition = null, + string|null $enumType = null, + array $options = [], + ): static { + $this->cm->setDiscriminatorColumn( + [ + 'name' => $name, + 'type' => $type, + 'length' => $length, + 'columnDefinition' => $columnDefinition, + 'enumType' => $enumType, + 'options' => $options, + ], + ); + + return $this; + } + + /** + * Adds a subclass to this inheritance hierarchy. + * + * @return $this + */ + public function addDiscriminatorMapClass(string $name, string $class): static + { + $this->cm->addDiscriminatorMapClass($name, $class); + + return $this; + } + + /** + * Sets deferred explicit change tracking policy. + * + * @return $this + */ + public function setChangeTrackingPolicyDeferredExplicit(): static + { + $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); + + return $this; + } + + /** + * Adds lifecycle event. + * + * @return $this + */ + public function addLifecycleEvent(string $methodName, string $event): static + { + $this->cm->addLifecycleCallback($methodName, $event); + + return $this; + } + + /** + * Adds Field. + * + * @psalm-param array $mapping + * + * @return $this + */ + public function addField(string $name, string $type, array $mapping = []): static + { + $mapping['fieldName'] = $name; + $mapping['type'] = $type; + + $this->cm->mapField($mapping); + + return $this; + } + + /** + * Creates a field builder. + */ + public function createField(string $name, string $type): FieldBuilder + { + return new FieldBuilder( + $this, + [ + 'fieldName' => $name, + 'type' => $type, + ], + ); + } + + /** + * Creates an embedded builder. + */ + public function createEmbedded(string $fieldName, string $class): EmbeddedBuilder + { + return new EmbeddedBuilder( + $this, + [ + 'fieldName' => $fieldName, + 'class' => $class, + 'columnPrefix' => null, + ], + ); + } + + /** + * Adds a simple many to one association, optionally with the inversed by field. + */ + public function addManyToOne( + string $name, + string $targetEntity, + string|null $inversedBy = null, + ): ClassMetadataBuilder { + $builder = $this->createManyToOne($name, $targetEntity); + + if ($inversedBy !== null) { + $builder->inversedBy($inversedBy); + } + + return $builder->build(); + } + + /** + * Creates a ManyToOne Association Builder. + * + * Note: This method does not add the association, you have to call build() on the AssociationBuilder. + */ + public function createManyToOne(string $name, string $targetEntity): AssociationBuilder + { + return new AssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::MANY_TO_ONE, + ); + } + + /** + * Creates a OneToOne Association Builder. + */ + public function createOneToOne(string $name, string $targetEntity): AssociationBuilder + { + return new AssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::ONE_TO_ONE, + ); + } + + /** + * Adds simple inverse one-to-one association. + */ + public function addInverseOneToOne(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder + { + $builder = $this->createOneToOne($name, $targetEntity); + $builder->mappedBy($mappedBy); + + return $builder->build(); + } + + /** + * Adds simple owning one-to-one association. + */ + public function addOwningOneToOne( + string $name, + string $targetEntity, + string|null $inversedBy = null, + ): ClassMetadataBuilder { + $builder = $this->createOneToOne($name, $targetEntity); + + if ($inversedBy !== null) { + $builder->inversedBy($inversedBy); + } + + return $builder->build(); + } + + /** + * Creates a ManyToMany Association Builder. + */ + public function createManyToMany(string $name, string $targetEntity): ManyToManyAssociationBuilder + { + return new ManyToManyAssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::MANY_TO_MANY, + ); + } + + /** + * Adds a simple owning many to many association. + */ + public function addOwningManyToMany( + string $name, + string $targetEntity, + string|null $inversedBy = null, + ): ClassMetadataBuilder { + $builder = $this->createManyToMany($name, $targetEntity); + + if ($inversedBy !== null) { + $builder->inversedBy($inversedBy); + } + + return $builder->build(); + } + + /** + * Adds a simple inverse many to many association. + */ + public function addInverseManyToMany(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder + { + $builder = $this->createManyToMany($name, $targetEntity); + $builder->mappedBy($mappedBy); + + return $builder->build(); + } + + /** + * Creates a one to many association builder. + */ + public function createOneToMany(string $name, string $targetEntity): OneToManyAssociationBuilder + { + return new OneToManyAssociationBuilder( + $this, + [ + 'fieldName' => $name, + 'targetEntity' => $targetEntity, + ], + ClassMetadata::ONE_TO_MANY, + ); + } + + /** + * Adds simple OneToMany association. + */ + public function addOneToMany(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder + { + $builder = $this->createOneToMany($name, $targetEntity); + $builder->mappedBy($mappedBy); + + return $builder->build(); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php new file mode 100644 index 0000000..b9d2127 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/EmbeddedBuilder.php @@ -0,0 +1,46 @@ +mapping['columnPrefix'] = $columnPrefix; + + return $this; + } + + /** + * Finalizes this embeddable and attach it to the ClassMetadata. + * + * Without this call an EmbeddedBuilder has no effect on the ClassMetadata. + */ + public function build(): ClassMetadataBuilder + { + $cm = $this->builder->getClassMetadata(); + + $cm->mapEmbedded($this->mapping); + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php new file mode 100644 index 0000000..a0b14b9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/EntityListenerBuilder.php @@ -0,0 +1,55 @@ + true, + Events::postRemove => true, + Events::prePersist => true, + Events::postPersist => true, + Events::preUpdate => true, + Events::postUpdate => true, + Events::postLoad => true, + Events::preFlush => true, + ]; + + /** + * Lookup the entity class to find methods that match to event lifecycle names + * + * @param ClassMetadata $metadata The entity metadata. + * @param string $className The listener class name. + * + * @throws MappingException When the listener class not found. + */ + public static function bindEntityListener(ClassMetadata $metadata, string $className): void + { + $class = $metadata->fullyQualifiedClassName($className); + + if (! class_exists($class)) { + throw MappingException::entityListenerClassNotFound($class, $className); + } + + foreach (get_class_methods($class) as $method) { + if (! isset(self::EVENTS[$method])) { + continue; + } + + $metadata->addEntityListener($method, $class, $method); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php new file mode 100644 index 0000000..8326ff5 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/FieldBuilder.php @@ -0,0 +1,243 @@ +mapping['length'] = $length; + + return $this; + } + + /** + * Sets nullable. + * + * @return $this + */ + public function nullable(bool $flag = true): static + { + $this->mapping['nullable'] = $flag; + + return $this; + } + + /** + * Sets Unique. + * + * @return $this + */ + public function unique(bool $flag = true): static + { + $this->mapping['unique'] = $flag; + + return $this; + } + + /** + * Sets column name. + * + * @return $this + */ + public function columnName(string $name): static + { + $this->mapping['columnName'] = $name; + + return $this; + } + + /** + * Sets Precision. + * + * @return $this + */ + public function precision(int $p): static + { + $this->mapping['precision'] = $p; + + return $this; + } + + /** + * Sets insertable. + * + * @return $this + */ + public function insertable(bool $flag = true): self + { + if (! $flag) { + $this->mapping['notInsertable'] = true; + } + + return $this; + } + + /** + * Sets updatable. + * + * @return $this + */ + public function updatable(bool $flag = true): self + { + if (! $flag) { + $this->mapping['notUpdatable'] = true; + } + + return $this; + } + + /** + * Sets scale. + * + * @return $this + */ + public function scale(int $s): static + { + $this->mapping['scale'] = $s; + + return $this; + } + + /** + * Sets field as primary key. + * + * @return $this + */ + public function makePrimaryKey(): static + { + $this->mapping['id'] = true; + + return $this; + } + + /** + * Sets an option. + * + * @return $this + */ + public function option(string $name, mixed $value): static + { + $this->mapping['options'][$name] = $value; + + return $this; + } + + /** @return $this */ + public function generatedValue(string $strategy = 'AUTO'): static + { + $this->generatedValue = $strategy; + + return $this; + } + + /** + * Sets field versioned. + * + * @return $this + */ + public function isVersionField(): static + { + $this->version = true; + + return $this; + } + + /** + * Sets Sequence Generator. + * + * @return $this + */ + public function setSequenceGenerator(string $sequenceName, int $allocationSize = 1, int $initialValue = 1): static + { + $this->sequenceDef = [ + 'sequenceName' => $sequenceName, + 'allocationSize' => $allocationSize, + 'initialValue' => $initialValue, + ]; + + return $this; + } + + /** + * Sets column definition. + * + * @return $this + */ + public function columnDefinition(string $def): static + { + $this->mapping['columnDefinition'] = $def; + + return $this; + } + + /** + * Set the FQCN of the custom ID generator. + * This class must extend \Doctrine\ORM\Id\AbstractIdGenerator. + * + * @return $this + */ + public function setCustomIdGenerator(string $customIdGenerator): static + { + $this->customIdGenerator = $customIdGenerator; + + return $this; + } + + /** + * Finalizes this field and attach it to the ClassMetadata. + * + * Without this call a FieldBuilder has no effect on the ClassMetadata. + */ + public function build(): ClassMetadataBuilder + { + $cm = $this->builder->getClassMetadata(); + if ($this->generatedValue) { + $cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue)); + } + + if ($this->version) { + $cm->setVersionMapping($this->mapping); + } + + $cm->mapField($this->mapping); + if ($this->sequenceDef) { + $cm->setSequenceGeneratorDefinition($this->sequenceDef); + } + + if ($this->customIdGenerator) { + $cm->setCustomGeneratorDefinition(['class' => $this->customIdGenerator]); + } + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php new file mode 100644 index 0000000..b83a8ba --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/ManyToManyAssociationBuilder.php @@ -0,0 +1,73 @@ +joinTableName = $name; + + return $this; + } + + /** + * Adds Inverse Join Columns. + * + * @return $this + */ + public function addInverseJoinColumn( + string $columnName, + string $referencedColumnName, + bool $nullable = true, + bool $unique = false, + string|null $onDelete = null, + string|null $columnDef = null, + ): static { + $this->inverseJoinColumns[] = [ + 'name' => $columnName, + 'referencedColumnName' => $referencedColumnName, + 'nullable' => $nullable, + 'unique' => $unique, + 'onDelete' => $onDelete, + 'columnDefinition' => $columnDef, + ]; + + return $this; + } + + public function build(): ClassMetadataBuilder + { + $mapping = $this->mapping; + $mapping['joinTable'] = []; + if ($this->joinColumns) { + $mapping['joinTable']['joinColumns'] = $this->joinColumns; + } + + if ($this->inverseJoinColumns) { + $mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns; + } + + if ($this->joinTableName) { + $mapping['joinTable']['name'] = $this->joinTableName; + } + + $cm = $this->builder->getClassMetadata(); + $cm->mapManyToMany($mapping); + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php b/vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php new file mode 100644 index 0000000..077c558 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Builder/OneToManyAssociationBuilder.php @@ -0,0 +1,46 @@ + $fieldNames + * + * @return $this + */ + public function setOrderBy(array $fieldNames): static + { + $this->mapping['orderBy'] = $fieldNames; + + return $this; + } + + /** @return $this */ + public function setIndexBy(string $fieldName): static + { + $this->mapping['indexBy'] = $fieldName; + + return $this; + } + + public function build(): ClassMetadataBuilder + { + $mapping = $this->mapping; + if ($this->joinColumns) { + $mapping['joinColumns'] = $this->joinColumns; + } + + $cm = $this->builder->getClassMetadata(); + $cm->mapOneToMany($mapping); + + return $this->builder; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Cache.php b/vendor/doctrine/orm/src/Mapping/Cache.php new file mode 100644 index 0000000..3161ab3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Cache.php @@ -0,0 +1,19 @@ + $typedFieldMappers */ + private readonly array $typedFieldMappers; + + public function __construct(TypedFieldMapper ...$typedFieldMappers) + { + self::validateVariadicParameter($typedFieldMappers); + + $this->typedFieldMappers = $typedFieldMappers; + } + + /** + * {@inheritDoc} + */ + public function validateAndComplete(array $mapping, ReflectionProperty $field): array + { + foreach ($this->typedFieldMappers as $typedFieldMapper) { + $mapping = $typedFieldMapper->validateAndComplete($mapping, $field); + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php b/vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php new file mode 100644 index 0000000..7181d9f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ChangeTrackingPolicy.php @@ -0,0 +1,17 @@ +ClassMetadata instance holds all the object-relational mapping metadata + * of an entity and its associations. + * + * Once populated, ClassMetadata instances are usually cached in a serialized form. + * + * IMPORTANT NOTE: + * + * The fields of this class are only public for 2 reasons: + * 1) To allow fast READ access. + * 2) To drastically reduce the size of a serialized instance (private/protected members + * get the whole class name, namespace inclusive, prepended to every property in + * the serialized representation). + * + * @psalm-type ConcreteAssociationMapping = OneToOneOwningSideMapping|OneToOneInverseSideMapping|ManyToOneAssociationMapping|OneToManyAssociationMapping|ManyToManyOwningSideMapping|ManyToManyInverseSideMapping + * @template-covariant T of object + * @template-implements PersistenceClassMetadata + */ +class ClassMetadata implements PersistenceClassMetadata, Stringable +{ + /* The inheritance mapping types */ + /** + * NONE means the class does not participate in an inheritance hierarchy + * and therefore does not need an inheritance mapping type. + */ + public const INHERITANCE_TYPE_NONE = 1; + + /** + * JOINED means the class will be persisted according to the rules of + * Class Table Inheritance. + */ + public const INHERITANCE_TYPE_JOINED = 2; + + /** + * SINGLE_TABLE means the class will be persisted according to the rules of + * Single Table Inheritance. + */ + public const INHERITANCE_TYPE_SINGLE_TABLE = 3; + + /* The Id generator types. */ + /** + * AUTO means the generator type will depend on what the used platform prefers. + * Offers full portability. + */ + public const GENERATOR_TYPE_AUTO = 1; + + /** + * SEQUENCE means a separate sequence object will be used. Platforms that do + * not have native sequence support may emulate it. Full portability is currently + * not guaranteed. + */ + public const GENERATOR_TYPE_SEQUENCE = 2; + + /** + * IDENTITY means an identity column is used for id generation. The database + * will fill in the id column on insertion. Platforms that do not support + * native identity columns may emulate them. Full portability is currently + * not guaranteed. + */ + public const GENERATOR_TYPE_IDENTITY = 4; + + /** + * NONE means the class does not have a generated id. That means the class + * must have a natural, manually assigned id. + */ + public const GENERATOR_TYPE_NONE = 5; + + /** + * CUSTOM means that customer will use own ID generator that supposedly work + */ + public const GENERATOR_TYPE_CUSTOM = 7; + + /** + * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time + * by doing a property-by-property comparison with the original data. This will + * be done for all entities that are in MANAGED state at commit-time. + * + * This is the default change tracking policy. + */ + public const CHANGETRACKING_DEFERRED_IMPLICIT = 1; + + /** + * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time + * by doing a property-by-property comparison with the original data. This will + * be done only for entities that were explicitly saved (through persist() or a cascade). + */ + public const CHANGETRACKING_DEFERRED_EXPLICIT = 2; + + /** + * Specifies that an association is to be fetched when it is first accessed. + */ + public const FETCH_LAZY = 2; + + /** + * Specifies that an association is to be fetched when the owner of the + * association is fetched. + */ + public const FETCH_EAGER = 3; + + /** + * Specifies that an association is to be fetched lazy (on first access) and that + * commands such as Collection#count, Collection#slice are issued directly against + * the database if the collection is not yet initialized. + */ + public const FETCH_EXTRA_LAZY = 4; + + /** + * Identifies a one-to-one association. + */ + public const ONE_TO_ONE = 1; + + /** + * Identifies a many-to-one association. + */ + public const MANY_TO_ONE = 2; + + /** + * Identifies a one-to-many association. + */ + public const ONE_TO_MANY = 4; + + /** + * Identifies a many-to-many association. + */ + public const MANY_TO_MANY = 8; + + /** + * Combined bitmask for to-one (single-valued) associations. + */ + public const TO_ONE = 3; + + /** + * Combined bitmask for to-many (collection-valued) associations. + */ + public const TO_MANY = 12; + + /** + * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks, + */ + public const CACHE_USAGE_READ_ONLY = 1; + + /** + * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes. + */ + public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2; + + /** + * Read Write Attempts to lock the entity before update/delete. + */ + public const CACHE_USAGE_READ_WRITE = 3; + + /** + * The value of this column is never generated by the database. + */ + public const GENERATED_NEVER = 0; + + /** + * The value of this column is generated by the database on INSERT, but not on UPDATE. + */ + public const GENERATED_INSERT = 1; + + /** + * The value of this column is generated by the database on both INSERT and UDPATE statements. + */ + public const GENERATED_ALWAYS = 2; + + /** + * READ-ONLY: The namespace the entity class is contained in. + * + * @todo Not really needed. Usage could be localized. + */ + public string|null $namespace = null; + + /** + * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance + * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same + * as {@link $name}. + * + * @psalm-var class-string + */ + public string $rootEntityName; + + /** + * READ-ONLY: The definition of custom generator. Only used for CUSTOM + * generator type + * + * The definition has the following structure: + * + * array( + * 'class' => 'ClassName', + * ) + * + * + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition + * @var array|null + */ + public array|null $customGeneratorDefinition = null; + + /** + * The name of the custom repository class used for the entity class. + * (Optional). + * + * @psalm-var ?class-string + */ + public string|null $customRepositoryClassName = null; + + /** + * READ-ONLY: Whether this class describes the mapping of a mapped superclass. + */ + public bool $isMappedSuperclass = false; + + /** + * READ-ONLY: Whether this class describes the mapping of an embeddable class. + */ + public bool $isEmbeddedClass = false; + + /** + * READ-ONLY: The names of the parent entity classes (ancestors), starting with the + * nearest one and ending with the root entity class. + * + * @psalm-var list + */ + public array $parentClasses = []; + + /** + * READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all + * entity subclasses of this class. These may also be abstract classes. + * + * This list is used, for example, to enumerate all necessary tables in JTI when querying for root + * or subclass entities, or to gather all fields comprised in an entity inheritance tree. + * + * For classes that do not use STI/JTI, this list is empty. + * + * Implementation note: + * + * In PHP, there is no general way to discover all subclasses of a given class at runtime. For that + * reason, the list of classes given in the discriminator map at the root entity is considered + * authoritative. The discriminator map must contain all concrete classes that can + * appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract + * entity classes, users are not required to list such classes with a discriminator value. + * + * The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the + * root entity has been loaded. + * + * For subclasses of such root entities, the list can be reused/passed downwards, it only needs to + * be filtered accordingly (only keep remaining subclasses) + * + * @psalm-var list + */ + public array $subClasses = []; + + /** + * READ-ONLY: The names of all embedded classes based on properties. + * + * @psalm-var array + */ + public array $embeddedClasses = []; + + /** + * READ-ONLY: The field names of all fields that are part of the identifier/primary key + * of the mapped entity class. + * + * @psalm-var list + */ + public array $identifier = []; + + /** + * READ-ONLY: The inheritance mapping type used by the class. + * + * @psalm-var self::INHERITANCE_TYPE_* + */ + public int $inheritanceType = self::INHERITANCE_TYPE_NONE; + + /** + * READ-ONLY: The Id generator type used by the class. + * + * @psalm-var self::GENERATOR_TYPE_* + */ + public int $generatorType = self::GENERATOR_TYPE_NONE; + + /** + * READ-ONLY: The field mappings of the class. + * Keys are field names and values are FieldMapping instances + * + * @var array + */ + public array $fieldMappings = []; + + /** + * READ-ONLY: An array of field names. Used to look up field names from column names. + * Keys are column names and values are field names. + * + * @psalm-var array + */ + public array $fieldNames = []; + + /** + * READ-ONLY: A map of field names to column names. Keys are field names and values column names. + * Used to look up column names from field names. + * This is the reverse lookup map of $_fieldNames. + * + * @deprecated 3.0 Remove this. + * + * @var mixed[] + */ + public array $columnNames = []; + + /** + * READ-ONLY: The discriminator value of this class. + * + * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies + * where a discriminator column is used. + * + * @see discriminatorColumn + */ + public mixed $discriminatorValue = null; + + /** + * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. + * + * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies + * where a discriminator column is used. + * + * @see discriminatorColumn + * + * @var array + * + * @psalm-var array + */ + public array $discriminatorMap = []; + + /** + * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE + * inheritance mappings. + */ + public DiscriminatorColumnMapping|null $discriminatorColumn = null; + + /** + * READ-ONLY: The primary table definition. The definition is an array with the + * following entries: + * + * name => + * schema => + * indexes => array + * uniqueConstraints => array + * + * @var mixed[] + * @psalm-var array{ + * name: string, + * schema?: string, + * indexes?: array, + * uniqueConstraints?: array, + * options?: array, + * quoted?: bool + * } + */ + public array $table; + + /** + * READ-ONLY: The registered lifecycle callbacks for entities of this class. + * + * @psalm-var array> + */ + public array $lifecycleCallbacks = []; + + /** + * READ-ONLY: The registered entity listeners. + * + * @psalm-var array> + */ + public array $entityListeners = []; + + /** + * READ-ONLY: The association mappings of this class. + * + * A join table definition has the following structure: + *
+     * array(
+     *     'name' => ,
+     *      'joinColumns' => array(),
+     *      'inverseJoinColumns' => array()
+     * )
+     * 
+ * + * @psalm-var array + */ + public array $associationMappings = []; + + /** + * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite. + */ + public bool $isIdentifierComposite = false; + + /** + * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association. + * + * This flag is necessary because some code blocks require special treatment of this cases. + */ + public bool $containsForeignIdentifier = false; + + /** + * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one ENUM type. + * + * This flag is necessary because some code blocks require special treatment of this cases. + */ + public bool $containsEnumIdentifier = false; + + /** + * READ-ONLY: The ID generator used for generating IDs for this class. + * + * @todo Remove! + */ + public AbstractIdGenerator $idGenerator; + + /** + * READ-ONLY: The definition of the sequence generator of this class. Only used for the + * SEQUENCE generation strategy. + * + * The definition has the following structure: + * + * array( + * 'sequenceName' => 'name', + * 'allocationSize' => '20', + * 'initialValue' => '1' + * ) + * + * + * @var array|null + * @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}|null + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition + */ + public array|null $sequenceGeneratorDefinition = null; + + /** + * READ-ONLY: The policy used for change-tracking on entities of this class. + */ + public int $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; + + /** + * READ-ONLY: A Flag indicating whether one or more columns of this class + * have to be reloaded after insert / update operations. + */ + public bool $requiresFetchAfterChange = false; + + /** + * READ-ONLY: A flag for whether or not instances of this class are to be versioned + * with optimistic locking. + */ + public bool $isVersioned = false; + + /** + * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). + */ + public string|null $versionField = null; + + /** @var mixed[]|null */ + public array|null $cache = null; + + /** + * The ReflectionClass instance of the mapped class. + * + * @var ReflectionClass|null + */ + public ReflectionClass|null $reflClass = null; + + /** + * Is this entity marked as "read-only"? + * + * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance + * optimization for entities that are immutable, either in your domain or through the relation database + * (coming from a view, or a history table for example). + */ + public bool $isReadOnly = false; + + /** + * NamingStrategy determining the default column and table names. + */ + protected NamingStrategy $namingStrategy; + + /** + * The ReflectionProperty instances of the mapped class. + * + * @var array + */ + public array $reflFields = []; + + private InstantiatorInterface|null $instantiator = null; + + private readonly TypedFieldMapper $typedFieldMapper; + + /** + * Initializes a new ClassMetadata instance that will hold the object-relational mapping + * metadata of the class with the given name. + * + * @param string $name The name of the entity class the new instance is used for. + * @psalm-param class-string $name + */ + public function __construct(public string $name, NamingStrategy|null $namingStrategy = null, TypedFieldMapper|null $typedFieldMapper = null) + { + $this->rootEntityName = $name; + $this->namingStrategy = $namingStrategy ?? new DefaultNamingStrategy(); + $this->instantiator = new Instantiator(); + $this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper(); + } + + /** + * Gets the ReflectionProperties of the mapped class. + * + * @return ReflectionProperty[]|null[] An array of ReflectionProperty instances. + * @psalm-return array + */ + public function getReflectionProperties(): array + { + return $this->reflFields; + } + + /** + * Gets a ReflectionProperty for a specific field of the mapped class. + */ + public function getReflectionProperty(string $name): ReflectionProperty|null + { + return $this->reflFields[$name]; + } + + /** + * Gets the ReflectionProperty for the single identifier field. + * + * @throws BadMethodCallException If the class has a composite identifier. + */ + public function getSingleIdReflectionProperty(): ReflectionProperty|null + { + if ($this->isIdentifierComposite) { + throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.'); + } + + return $this->reflFields[$this->identifier[0]]; + } + + /** + * Extracts the identifier values of an entity of this class. + * + * For composite identifiers, the identifier values are returned as an array + * with the same order as the field order in {@link identifier}. + * + * @return array + */ + public function getIdentifierValues(object $entity): array + { + if ($this->isIdentifierComposite) { + $id = []; + + foreach ($this->identifier as $idField) { + $value = $this->reflFields[$idField]->getValue($entity); + + if ($value !== null) { + $id[$idField] = $value; + } + } + + return $id; + } + + $id = $this->identifier[0]; + $value = $this->reflFields[$id]->getValue($entity); + + if ($value === null) { + return []; + } + + return [$id => $value]; + } + + /** + * Populates the entity identifier of an entity. + * + * @psalm-param array $id + * + * @todo Rename to assignIdentifier() + */ + public function setIdentifierValues(object $entity, array $id): void + { + foreach ($id as $idField => $idValue) { + $this->reflFields[$idField]->setValue($entity, $idValue); + } + } + + /** + * Sets the specified field to the specified value on the given entity. + */ + public function setFieldValue(object $entity, string $field, mixed $value): void + { + $this->reflFields[$field]->setValue($entity, $value); + } + + /** + * Gets the specified field's value off the given entity. + */ + public function getFieldValue(object $entity, string $field): mixed + { + return $this->reflFields[$field]->getValue($entity); + } + + /** + * Creates a string representation of this instance. + * + * @return string The string representation of this instance. + * + * @todo Construct meaningful string representation. + */ + public function __toString(): string + { + return self::class . '@' . spl_object_id($this); + } + + /** + * Determines which fields get serialized. + * + * It is only serialized what is necessary for best unserialization performance. + * That means any metadata properties that are not set or empty or simply have + * their default value are NOT serialized. + * + * Parts that are also NOT serialized because they can not be properly unserialized: + * - reflClass (ReflectionClass) + * - reflFields (ReflectionProperty array) + * + * @return string[] The names of all the fields that should be serialized. + */ + public function __sleep(): array + { + // This metadata is always serialized/cached. + $serialized = [ + 'associationMappings', + 'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName'] + 'fieldMappings', + 'fieldNames', + 'embeddedClasses', + 'identifier', + 'isIdentifierComposite', // TODO: REMOVE + 'name', + 'namespace', // TODO: REMOVE + 'table', + 'rootEntityName', + 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. + ]; + + // The rest of the metadata is only serialized if necessary. + if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) { + $serialized[] = 'changeTrackingPolicy'; + } + + if ($this->customRepositoryClassName) { + $serialized[] = 'customRepositoryClassName'; + } + + if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE) { + $serialized[] = 'inheritanceType'; + $serialized[] = 'discriminatorColumn'; + $serialized[] = 'discriminatorValue'; + $serialized[] = 'discriminatorMap'; + $serialized[] = 'parentClasses'; + $serialized[] = 'subClasses'; + } + + if ($this->generatorType !== self::GENERATOR_TYPE_NONE) { + $serialized[] = 'generatorType'; + if ($this->generatorType === self::GENERATOR_TYPE_SEQUENCE) { + $serialized[] = 'sequenceGeneratorDefinition'; + } + } + + if ($this->isMappedSuperclass) { + $serialized[] = 'isMappedSuperclass'; + } + + if ($this->isEmbeddedClass) { + $serialized[] = 'isEmbeddedClass'; + } + + if ($this->containsForeignIdentifier) { + $serialized[] = 'containsForeignIdentifier'; + } + + if ($this->containsEnumIdentifier) { + $serialized[] = 'containsEnumIdentifier'; + } + + if ($this->isVersioned) { + $serialized[] = 'isVersioned'; + $serialized[] = 'versionField'; + } + + if ($this->lifecycleCallbacks) { + $serialized[] = 'lifecycleCallbacks'; + } + + if ($this->entityListeners) { + $serialized[] = 'entityListeners'; + } + + if ($this->isReadOnly) { + $serialized[] = 'isReadOnly'; + } + + if ($this->customGeneratorDefinition) { + $serialized[] = 'customGeneratorDefinition'; + } + + if ($this->cache) { + $serialized[] = 'cache'; + } + + if ($this->requiresFetchAfterChange) { + $serialized[] = 'requiresFetchAfterChange'; + } + + return $serialized; + } + + /** + * Creates a new instance of the mapped class, without invoking the constructor. + */ + public function newInstance(): object + { + return $this->instantiator->instantiate($this->name); + } + + /** + * Restores some state that can not be serialized/unserialized. + */ + public function wakeupReflection(ReflectionService $reflService): void + { + // Restore ReflectionClass and properties + $this->reflClass = $reflService->getClass($this->name); + $this->instantiator = $this->instantiator ?: new Instantiator(); + + $parentReflFields = []; + + foreach ($this->embeddedClasses as $property => $embeddedClass) { + if (isset($embeddedClass->declaredField)) { + assert($embeddedClass->originalField !== null); + $childProperty = $this->getAccessibleProperty( + $reflService, + $this->embeddedClasses[$embeddedClass->declaredField]->class, + $embeddedClass->originalField, + ); + assert($childProperty !== null); + $parentReflFields[$property] = new ReflectionEmbeddedProperty( + $parentReflFields[$embeddedClass->declaredField], + $childProperty, + $this->embeddedClasses[$embeddedClass->declaredField]->class, + ); + + continue; + } + + $fieldRefl = $this->getAccessibleProperty( + $reflService, + $embeddedClass->declared ?? $this->name, + $property, + ); + + $parentReflFields[$property] = $fieldRefl; + $this->reflFields[$property] = $fieldRefl; + } + + foreach ($this->fieldMappings as $field => $mapping) { + if (isset($mapping->declaredField) && isset($parentReflFields[$mapping->declaredField])) { + assert($mapping->originalField !== null); + assert($mapping->originalClass !== null); + $childProperty = $this->getAccessibleProperty($reflService, $mapping->originalClass, $mapping->originalField); + assert($childProperty !== null); + + if (isset($mapping->enumType)) { + $childProperty = new EnumReflectionProperty( + $childProperty, + $mapping->enumType, + ); + } + + $this->reflFields[$field] = new ReflectionEmbeddedProperty( + $parentReflFields[$mapping->declaredField], + $childProperty, + $mapping->originalClass, + ); + continue; + } + + $this->reflFields[$field] = isset($mapping->declared) + ? $this->getAccessibleProperty($reflService, $mapping->declared, $field) + : $this->getAccessibleProperty($reflService, $this->name, $field); + + if (isset($mapping->enumType) && $this->reflFields[$field] !== null) { + $this->reflFields[$field] = new EnumReflectionProperty( + $this->reflFields[$field], + $mapping->enumType, + ); + } + } + + foreach ($this->associationMappings as $field => $mapping) { + $this->reflFields[$field] = isset($mapping->declared) + ? $this->getAccessibleProperty($reflService, $mapping->declared, $field) + : $this->getAccessibleProperty($reflService, $this->name, $field); + } + } + + /** + * Initializes a new ClassMetadata instance that will hold the object-relational mapping + * metadata of the class with the given name. + * + * @param ReflectionService $reflService The reflection service. + */ + public function initializeReflection(ReflectionService $reflService): void + { + $this->reflClass = $reflService->getClass($this->name); + $this->namespace = $reflService->getClassNamespace($this->name); + + if ($this->reflClass) { + $this->name = $this->rootEntityName = $this->reflClass->name; + } + + $this->table['name'] = $this->namingStrategy->classToTableName($this->name); + } + + /** + * Validates Identifier. + * + * @throws MappingException + */ + public function validateIdentifier(): void + { + if ($this->isMappedSuperclass || $this->isEmbeddedClass) { + return; + } + + // Verify & complete identifier mapping + if (! $this->identifier) { + throw MappingException::identifierRequired($this->name); + } + + if ($this->usesIdGenerator() && $this->isIdentifierComposite) { + throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name); + } + } + + /** + * Validates association targets actually exist. + * + * @throws MappingException + */ + public function validateAssociations(): void + { + foreach ($this->associationMappings as $mapping) { + if ( + ! class_exists($mapping->targetEntity) + && ! interface_exists($mapping->targetEntity) + && ! trait_exists($mapping->targetEntity) + ) { + throw MappingException::invalidTargetEntityClass($mapping->targetEntity, $this->name, $mapping->fieldName); + } + } + } + + /** + * Validates lifecycle callbacks. + * + * @throws MappingException + */ + public function validateLifecycleCallbacks(ReflectionService $reflService): void + { + foreach ($this->lifecycleCallbacks as $callbacks) { + foreach ($callbacks as $callbackFuncName) { + if (! $reflService->hasPublicMethod($this->name, $callbackFuncName)) { + throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName); + } + } + } + } + + /** + * {@inheritDoc} + * + * Can return null when using static reflection, in violation of the LSP + */ + public function getReflectionClass(): ReflectionClass|null + { + return $this->reflClass; + } + + /** @psalm-param array{usage?: mixed, region?: mixed} $cache */ + public function enableCache(array $cache): void + { + if (! isset($cache['usage'])) { + $cache['usage'] = self::CACHE_USAGE_READ_ONLY; + } + + if (! isset($cache['region'])) { + $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)); + } + + $this->cache = $cache; + } + + /** @psalm-param array{usage?: int, region?: string} $cache */ + public function enableAssociationCache(string $fieldName, array $cache): void + { + $this->associationMappings[$fieldName]->cache = $this->getAssociationCacheDefaults($fieldName, $cache); + } + + /** + * @psalm-param array{usage?: int, region?: string|null} $cache + * + * @return int[]|string[] + * @psalm-return array{usage: int, region: string|null} + */ + public function getAssociationCacheDefaults(string $fieldName, array $cache): array + { + if (! isset($cache['usage'])) { + $cache['usage'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY; + } + + if (! isset($cache['region'])) { + $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName; + } + + return $cache; + } + + /** + * Sets the change tracking policy used by this class. + */ + public function setChangeTrackingPolicy(int $policy): void + { + $this->changeTrackingPolicy = $policy; + } + + /** + * Whether the change tracking policy of this class is "deferred explicit". + */ + public function isChangeTrackingDeferredExplicit(): bool + { + return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT; + } + + /** + * Whether the change tracking policy of this class is "deferred implicit". + */ + public function isChangeTrackingDeferredImplicit(): bool + { + return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT; + } + + /** + * Checks whether a field is part of the identifier/primary key field(s). + */ + public function isIdentifier(string $fieldName): bool + { + if (! $this->identifier) { + return false; + } + + if (! $this->isIdentifierComposite) { + return $fieldName === $this->identifier[0]; + } + + return in_array($fieldName, $this->identifier, true); + } + + public function isUniqueField(string $fieldName): bool + { + $mapping = $this->getFieldMapping($fieldName); + + return $mapping !== false && isset($mapping->unique) && $mapping->unique; + } + + public function isNullable(string $fieldName): bool + { + $mapping = $this->getFieldMapping($fieldName); + + return $mapping !== false && isset($mapping->nullable) && $mapping->nullable; + } + + /** + * Gets a column name for a field name. + * If the column name for the field cannot be found, the given field name + * is returned. + */ + public function getColumnName(string $fieldName): string + { + return $this->columnNames[$fieldName] ?? $fieldName; + } + + /** + * Gets the mapping of a (regular) field that holds some data but not a + * reference to another object. + * + * @throws MappingException + */ + public function getFieldMapping(string $fieldName): FieldMapping + { + if (! isset($this->fieldMappings[$fieldName])) { + throw MappingException::mappingNotFound($this->name, $fieldName); + } + + return $this->fieldMappings[$fieldName]; + } + + /** + * Gets the mapping of an association. + * + * @see ClassMetadata::$associationMappings + * + * @param string $fieldName The field name that represents the association in + * the object model. + * + * @throws MappingException + */ + public function getAssociationMapping(string $fieldName): AssociationMapping + { + if (! isset($this->associationMappings[$fieldName])) { + throw MappingException::mappingNotFound($this->name, $fieldName); + } + + return $this->associationMappings[$fieldName]; + } + + /** + * Gets all association mappings of the class. + * + * @psalm-return array + */ + public function getAssociationMappings(): array + { + return $this->associationMappings; + } + + /** + * Gets the field name for a column name. + * If no field name can be found the column name is returned. + * + * @return string The column alias. + */ + public function getFieldName(string $columnName): string + { + return $this->fieldNames[$columnName] ?? $columnName; + } + + /** + * Checks whether given property has type + */ + private function isTypedProperty(string $name): bool + { + return isset($this->reflClass) + && $this->reflClass->hasProperty($name) + && $this->reflClass->getProperty($name)->hasType(); + } + + /** + * Validates & completes the given field mapping based on typed property. + * + * @param array{fieldName: string, type?: string} $mapping The field mapping to validate & complete. + * + * @return array{fieldName: string, enumType?: class-string, type?: string} The updated mapping. + */ + private function validateAndCompleteTypedFieldMapping(array $mapping): array + { + $field = $this->reflClass->getProperty($mapping['fieldName']); + + $mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field); + + return $mapping; + } + + /** + * Validates & completes the basic mapping information based on typed property. + * + * @param array{type: self::ONE_TO_ONE|self::MANY_TO_ONE|self::ONE_TO_MANY|self::MANY_TO_MANY, fieldName: string, targetEntity?: class-string} $mapping The mapping. + * + * @return mixed[] The updated mapping. + */ + private function validateAndCompleteTypedAssociationMapping(array $mapping): array + { + $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); + + if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) { + return $mapping; + } + + if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) { + $mapping['targetEntity'] = $type->getName(); + } + + return $mapping; + } + + /** + * Validates & completes the given field mapping. + * + * @psalm-param array{ + * fieldName?: string, + * columnName?: string, + * id?: bool, + * generated?: self::GENERATED_*, + * enumType?: class-string, + * } $mapping The field mapping to validate & complete. + * + * @return FieldMapping The updated mapping. + * + * @throws MappingException + */ + protected function validateAndCompleteFieldMapping(array $mapping): FieldMapping + { + // Check mandatory fields + if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { + throw MappingException::missingFieldName($this->name); + } + + if ($this->isTypedProperty($mapping['fieldName'])) { + $mapping = $this->validateAndCompleteTypedFieldMapping($mapping); + } + + if (! isset($mapping['type'])) { + // Default to string + $mapping['type'] = 'string'; + } + + // Complete fieldName and columnName mapping + if (! isset($mapping['columnName'])) { + $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name); + } + + $mapping = FieldMapping::fromMappingArray($mapping); + + if ($mapping->columnName[0] === '`') { + $mapping->columnName = trim($mapping->columnName, '`'); + $mapping->quoted = true; + } + + $this->columnNames[$mapping->fieldName] = $mapping->columnName; + + if (isset($this->fieldNames[$mapping->columnName]) || ($this->discriminatorColumn && $this->discriminatorColumn->name === $mapping->columnName)) { + throw MappingException::duplicateColumnName($this->name, $mapping->columnName); + } + + $this->fieldNames[$mapping->columnName] = $mapping->fieldName; + + // Complete id mapping + if (isset($mapping->id) && $mapping->id === true) { + if ($this->versionField === $mapping->fieldName) { + throw MappingException::cannotVersionIdField($this->name, $mapping->fieldName); + } + + if (! in_array($mapping->fieldName, $this->identifier, true)) { + $this->identifier[] = $mapping->fieldName; + } + + // Check for composite key + if (! $this->isIdentifierComposite && count($this->identifier) > 1) { + $this->isIdentifierComposite = true; + } + } + + if (isset($mapping->generated)) { + if (! in_array($mapping->generated, [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) { + throw MappingException::invalidGeneratedMode($mapping->generated); + } + + if ($mapping->generated === self::GENERATED_NEVER) { + unset($mapping->generated); + } + } + + if (isset($mapping->enumType)) { + if (! enum_exists($mapping->enumType)) { + throw MappingException::nonEnumTypeMapped($this->name, $mapping->fieldName, $mapping->enumType); + } + + if (! empty($mapping->id)) { + $this->containsEnumIdentifier = true; + } + } + + return $mapping; + } + + /** + * Validates & completes the basic mapping information that is common to all + * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). + * + * @psalm-param array $mapping The mapping. + * + * @return ConcreteAssociationMapping + * + * @throws MappingException If something is wrong with the mapping. + */ + protected function _validateAndCompleteAssociationMapping(array $mapping): AssociationMapping + { + if (array_key_exists('mappedBy', $mapping) && $mapping['mappedBy'] === null) { + unset($mapping['mappedBy']); + } + + if (array_key_exists('inversedBy', $mapping) && $mapping['inversedBy'] === null) { + unset($mapping['inversedBy']); + } + + if (array_key_exists('joinColumns', $mapping) && in_array($mapping['joinColumns'], [null, []], true)) { + unset($mapping['joinColumns']); + } + + $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy + + if (empty($mapping['indexBy'])) { + unset($mapping['indexBy']); + } + + // If targetEntity is unqualified, assume it is in the same namespace as + // the sourceEntity. + $mapping['sourceEntity'] = $this->name; + + if ($this->isTypedProperty($mapping['fieldName'])) { + $mapping = $this->validateAndCompleteTypedAssociationMapping($mapping); + } + + if (isset($mapping['targetEntity'])) { + $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']); + $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); + } + + if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { + throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']); + } + + // Complete id mapping + if (isset($mapping['id']) && $mapping['id'] === true) { + if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { + throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); + } + + if (! in_array($mapping['fieldName'], $this->identifier, true)) { + if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) { + throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( + $mapping['targetEntity'], + $this->name, + $mapping['fieldName'], + ); + } + + assert(is_string($mapping['fieldName'])); + $this->identifier[] = $mapping['fieldName']; + $this->containsForeignIdentifier = true; + } + + // Check for composite key + if (! $this->isIdentifierComposite && count($this->identifier) > 1) { + $this->isIdentifierComposite = true; + } + + if ($this->cache && ! isset($mapping['cache'])) { + throw NonCacheableEntityAssociation::fromEntityAndField( + $this->name, + $mapping['fieldName'], + ); + } + } + + // Mandatory attributes for both sides + // Mandatory: fieldName, targetEntity + if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { + throw MappingException::missingFieldName($this->name); + } + + if (! isset($mapping['targetEntity'])) { + throw MappingException::missingTargetEntity($mapping['fieldName']); + } + + // Mandatory and optional attributes for either side + if (! isset($mapping['mappedBy'])) { + if (isset($mapping['joinTable'])) { + if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') { + $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`'); + $mapping['joinTable']['quoted'] = true; + } + } + } else { + $mapping['isOwningSide'] = false; + } + + if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { + throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']); + } + + // Fetch mode. Default fetch mode to LAZY, if not set. + if (! isset($mapping['fetch'])) { + $mapping['fetch'] = self::FETCH_LAZY; + } + + // Cascades + $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : []; + + $allCascades = ['remove', 'persist', 'refresh', 'detach']; + if (in_array('all', $cascades, true)) { + $cascades = $allCascades; + } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) { + throw MappingException::invalidCascadeOption( + array_diff($cascades, $allCascades), + $this->name, + $mapping['fieldName'], + ); + } + + $mapping['cascade'] = $cascades; + + switch ($mapping['type']) { + case self::ONE_TO_ONE: + if (isset($mapping['joinColumns']) && $mapping['joinColumns'] && ! $mapping['isOwningSide']) { + throw MappingException::joinColumnNotAllowedOnOneToOneInverseSide( + $this->name, + $mapping['fieldName'], + ); + } + + return $mapping['isOwningSide'] ? + OneToOneOwningSideMapping::fromMappingArrayAndName( + $mapping, + $this->namingStrategy, + $this->name, + $this->table ?? null, + $this->isInheritanceTypeSingleTable(), + ) : + OneToOneInverseSideMapping::fromMappingArrayAndName($mapping, $this->name); + + case self::MANY_TO_ONE: + return ManyToOneAssociationMapping::fromMappingArrayAndName( + $mapping, + $this->namingStrategy, + $this->name, + $this->table ?? null, + $this->isInheritanceTypeSingleTable(), + ); + + case self::ONE_TO_MANY: + return OneToManyAssociationMapping::fromMappingArrayAndName($mapping, $this->name); + + case self::MANY_TO_MANY: + if (isset($mapping['joinColumns'])) { + unset($mapping['joinColumns']); + } + + return $mapping['isOwningSide'] ? + ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy($mapping, $this->namingStrategy) : + ManyToManyInverseSideMapping::fromMappingArray($mapping); + + default: + throw MappingException::invalidAssociationType( + $this->name, + $mapping['fieldName'], + $mapping['type'], + ); + } + } + + /** + * {@inheritDoc} + */ + public function getIdentifierFieldNames(): array + { + return $this->identifier; + } + + /** + * Gets the name of the single id field. Note that this only works on + * entity classes that have a single-field pk. + * + * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. + */ + public function getSingleIdentifierFieldName(): string + { + if ($this->isIdentifierComposite) { + throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name); + } + + if (! isset($this->identifier[0])) { + throw MappingException::noIdDefined($this->name); + } + + return $this->identifier[0]; + } + + /** + * Gets the column name of the single id column. Note that this only works on + * entity classes that have a single-field pk. + * + * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. + */ + public function getSingleIdentifierColumnName(): string + { + return $this->getColumnName($this->getSingleIdentifierFieldName()); + } + + /** + * INTERNAL: + * Sets the mapped identifier/primary key fields of this class. + * Mainly used by the ClassMetadataFactory to assign inherited identifiers. + * + * @psalm-param list $identifier + */ + public function setIdentifier(array $identifier): void + { + $this->identifier = $identifier; + $this->isIdentifierComposite = (count($this->identifier) > 1); + } + + /** + * {@inheritDoc} + */ + public function getIdentifier(): array + { + return $this->identifier; + } + + public function hasField(string $fieldName): bool + { + return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]); + } + + /** + * Gets an array containing all the column names. + * + * @psalm-param list|null $fieldNames + * + * @return mixed[] + * @psalm-return list + */ + public function getColumnNames(array|null $fieldNames = null): array + { + if ($fieldNames === null) { + return array_keys($this->fieldNames); + } + + return array_values(array_map($this->getColumnName(...), $fieldNames)); + } + + /** + * Returns an array with all the identifier column names. + * + * @psalm-return list + */ + public function getIdentifierColumnNames(): array + { + $columnNames = []; + + foreach ($this->identifier as $idProperty) { + if (isset($this->fieldMappings[$idProperty])) { + $columnNames[] = $this->fieldMappings[$idProperty]->columnName; + + continue; + } + + // Association defined as Id field + assert($this->associationMappings[$idProperty]->isToOneOwningSide()); + $joinColumns = $this->associationMappings[$idProperty]->joinColumns; + $assocColumnNames = array_map(static fn (JoinColumnMapping $joinColumn): string => $joinColumn->name, $joinColumns); + + $columnNames = array_merge($columnNames, $assocColumnNames); + } + + return $columnNames; + } + + /** + * Sets the type of Id generator to use for the mapped class. + * + * @psalm-param self::GENERATOR_TYPE_* $generatorType + */ + public function setIdGeneratorType(int $generatorType): void + { + $this->generatorType = $generatorType; + } + + /** + * Checks whether the mapped class uses an Id generator. + */ + public function usesIdGenerator(): bool + { + return $this->generatorType !== self::GENERATOR_TYPE_NONE; + } + + public function isInheritanceTypeNone(): bool + { + return $this->inheritanceType === self::INHERITANCE_TYPE_NONE; + } + + /** + * Checks whether the mapped class uses the JOINED inheritance mapping strategy. + * + * @return bool TRUE if the class participates in a JOINED inheritance mapping, + * FALSE otherwise. + */ + public function isInheritanceTypeJoined(): bool + { + return $this->inheritanceType === self::INHERITANCE_TYPE_JOINED; + } + + /** + * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. + * + * @return bool TRUE if the class participates in a SINGLE_TABLE inheritance mapping, + * FALSE otherwise. + */ + public function isInheritanceTypeSingleTable(): bool + { + return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_TABLE; + } + + /** + * Checks whether the class uses an identity column for the Id generation. + */ + public function isIdGeneratorIdentity(): bool + { + return $this->generatorType === self::GENERATOR_TYPE_IDENTITY; + } + + /** + * Checks whether the class uses a sequence for id generation. + * + * @psalm-assert-if-true !null $this->sequenceGeneratorDefinition + */ + public function isIdGeneratorSequence(): bool + { + return $this->generatorType === self::GENERATOR_TYPE_SEQUENCE; + } + + /** + * Checks whether the class has a natural identifier/pk (which means it does + * not use any Id generator. + */ + public function isIdentifierNatural(): bool + { + return $this->generatorType === self::GENERATOR_TYPE_NONE; + } + + /** + * Gets the type of a field. + * + * @todo 3.0 Remove this. PersisterHelper should fix it somehow + */ + public function getTypeOfField(string $fieldName): string|null + { + return isset($this->fieldMappings[$fieldName]) + ? $this->fieldMappings[$fieldName]->type + : null; + } + + /** + * Gets the name of the primary table. + */ + public function getTableName(): string + { + return $this->table['name']; + } + + /** + * Gets primary table's schema name. + */ + public function getSchemaName(): string|null + { + return $this->table['schema'] ?? null; + } + + /** + * Gets the table name to use for temporary identifier tables of this class. + */ + public function getTemporaryIdTableName(): string + { + // replace dots with underscores because PostgreSQL creates temporary tables in a special schema + return str_replace('.', '_', $this->getTableName() . '_id_tmp'); + } + + /** + * Sets the mapped subclasses of this class. + * + * @psalm-param list $subclasses The names of all mapped subclasses. + */ + public function setSubclasses(array $subclasses): void + { + foreach ($subclasses as $subclass) { + $this->subClasses[] = $this->fullyQualifiedClassName($subclass); + } + } + + /** + * Sets the parent class names. Only entity classes may be given. + * + * Assumes that the class names in the passed array are in the order: + * directParent -> directParentParent -> directParentParentParent ... -> root. + * + * @psalm-param list $classNames + */ + public function setParentClasses(array $classNames): void + { + $this->parentClasses = $classNames; + + if (count($classNames) > 0) { + $this->rootEntityName = array_pop($classNames); + } + } + + /** + * Sets the inheritance type used by the class and its subclasses. + * + * @psalm-param self::INHERITANCE_TYPE_* $type + * + * @throws MappingException + */ + public function setInheritanceType(int $type): void + { + if (! $this->isInheritanceType($type)) { + throw MappingException::invalidInheritanceType($this->name, $type); + } + + $this->inheritanceType = $type; + } + + /** + * Sets the association to override association mapping of property for an entity relationship. + * + * @psalm-param array $overrideMapping + * + * @throws MappingException + */ + public function setAssociationOverride(string $fieldName, array $overrideMapping): void + { + if (! isset($this->associationMappings[$fieldName])) { + throw MappingException::invalidOverrideFieldName($this->name, $fieldName); + } + + $mapping = $this->associationMappings[$fieldName]->toArray(); + + if (isset($mapping['inherited'])) { + throw MappingException::illegalOverrideOfInheritedProperty( + $this->name, + $fieldName, + $mapping['inherited'], + ); + } + + if (isset($overrideMapping['joinColumns'])) { + $mapping['joinColumns'] = $overrideMapping['joinColumns']; + } + + if (isset($overrideMapping['inversedBy'])) { + $mapping['inversedBy'] = $overrideMapping['inversedBy']; + } + + if (isset($overrideMapping['joinTable'])) { + $mapping['joinTable'] = $overrideMapping['joinTable']; + } + + if (isset($overrideMapping['fetch'])) { + $mapping['fetch'] = $overrideMapping['fetch']; + } + + switch ($mapping['type']) { + case self::ONE_TO_ONE: + case self::MANY_TO_ONE: + $mapping['joinColumnFieldNames'] = []; + $mapping['sourceToTargetKeyColumns'] = []; + break; + case self::MANY_TO_MANY: + $mapping['relationToSourceKeyColumns'] = []; + $mapping['relationToTargetKeyColumns'] = []; + break; + } + + $this->associationMappings[$fieldName] = $this->_validateAndCompleteAssociationMapping($mapping); + } + + /** + * Sets the override for a mapped field. + * + * @psalm-param array $overrideMapping + * + * @throws MappingException + */ + public function setAttributeOverride(string $fieldName, array $overrideMapping): void + { + if (! isset($this->fieldMappings[$fieldName])) { + throw MappingException::invalidOverrideFieldName($this->name, $fieldName); + } + + $mapping = $this->fieldMappings[$fieldName]; + + if (isset($mapping->inherited)) { + throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName, $mapping->inherited); + } + + if (isset($mapping->id)) { + $overrideMapping['id'] = $mapping->id; + } + + if (isset($mapping->declared)) { + $overrideMapping['declared'] = $mapping->declared; + } + + if (! isset($overrideMapping['type'])) { + $overrideMapping['type'] = $mapping->type; + } + + if (! isset($overrideMapping['fieldName'])) { + $overrideMapping['fieldName'] = $mapping->fieldName; + } + + if ($overrideMapping['type'] !== $mapping->type) { + throw MappingException::invalidOverrideFieldType($this->name, $fieldName); + } + + unset($this->fieldMappings[$fieldName]); + unset($this->fieldNames[$mapping->columnName]); + unset($this->columnNames[$mapping->fieldName]); + + $overrideMapping = $this->validateAndCompleteFieldMapping($overrideMapping); + + $this->fieldMappings[$fieldName] = $overrideMapping; + } + + /** + * Checks whether a mapped field is inherited from an entity superclass. + */ + public function isInheritedField(string $fieldName): bool + { + return isset($this->fieldMappings[$fieldName]->inherited); + } + + /** + * Checks if this entity is the root in any entity-inheritance-hierarchy. + */ + public function isRootEntity(): bool + { + return $this->name === $this->rootEntityName; + } + + /** + * Checks whether a mapped association field is inherited from a superclass. + */ + public function isInheritedAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]->inherited); + } + + public function isInheritedEmbeddedClass(string $fieldName): bool + { + return isset($this->embeddedClasses[$fieldName]->inherited); + } + + /** + * Sets the name of the primary table the class is mapped to. + * + * @deprecated Use {@link setPrimaryTable}. + */ + public function setTableName(string $tableName): void + { + $this->table['name'] = $tableName; + } + + /** + * Sets the primary table definition. The provided array supports the + * following structure: + * + * name => (optional, defaults to class name) + * indexes => array of indexes (optional) + * uniqueConstraints => array of constraints (optional) + * + * If a key is omitted, the current value is kept. + * + * @psalm-param array $table The table description. + */ + public function setPrimaryTable(array $table): void + { + if (isset($table['name'])) { + // Split schema and table name from a table name like "myschema.mytable" + if (str_contains($table['name'], '.')) { + [$this->table['schema'], $table['name']] = explode('.', $table['name'], 2); + } + + if ($table['name'][0] === '`') { + $table['name'] = trim($table['name'], '`'); + $this->table['quoted'] = true; + } + + $this->table['name'] = $table['name']; + } + + if (isset($table['quoted'])) { + $this->table['quoted'] = $table['quoted']; + } + + if (isset($table['schema'])) { + $this->table['schema'] = $table['schema']; + } + + if (isset($table['indexes'])) { + $this->table['indexes'] = $table['indexes']; + } + + if (isset($table['uniqueConstraints'])) { + $this->table['uniqueConstraints'] = $table['uniqueConstraints']; + } + + if (isset($table['options'])) { + $this->table['options'] = $table['options']; + } + } + + /** + * Checks whether the given type identifies an inheritance type. + */ + private function isInheritanceType(int $type): bool + { + return $type === self::INHERITANCE_TYPE_NONE || + $type === self::INHERITANCE_TYPE_SINGLE_TABLE || + $type === self::INHERITANCE_TYPE_JOINED; + } + + /** + * Adds a mapped field to the class. + * + * @psalm-param array $mapping The field mapping. + * + * @throws MappingException + */ + public function mapField(array $mapping): void + { + $mapping = $this->validateAndCompleteFieldMapping($mapping); + $this->assertFieldNotMapped($mapping->fieldName); + + if (isset($mapping->generated)) { + $this->requiresFetchAfterChange = true; + } + + $this->fieldMappings[$mapping->fieldName] = $mapping; + } + + /** + * INTERNAL: + * Adds an association mapping without completing/validating it. + * This is mainly used to add inherited association mappings to derived classes. + * + * @param ConcreteAssociationMapping $mapping + * + * @throws MappingException + */ + public function addInheritedAssociationMapping(AssociationMapping $mapping/*, $owningClassName = null*/): void + { + if (isset($this->associationMappings[$mapping->fieldName])) { + throw MappingException::duplicateAssociationMapping($this->name, $mapping->fieldName); + } + + $this->associationMappings[$mapping->fieldName] = $mapping; + } + + /** + * INTERNAL: + * Adds a field mapping without completing/validating it. + * This is mainly used to add inherited field mappings to derived classes. + */ + public function addInheritedFieldMapping(FieldMapping $fieldMapping): void + { + $this->fieldMappings[$fieldMapping->fieldName] = $fieldMapping; + $this->columnNames[$fieldMapping->fieldName] = $fieldMapping->columnName; + $this->fieldNames[$fieldMapping->columnName] = $fieldMapping->fieldName; + + if (isset($fieldMapping->generated)) { + $this->requiresFetchAfterChange = true; + } + } + + /** + * Adds a one-to-one mapping. + * + * @param array $mapping The mapping. + */ + public function mapOneToOne(array $mapping): void + { + $mapping['type'] = self::ONE_TO_ONE; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Adds a one-to-many mapping. + * + * @psalm-param array $mapping The mapping. + */ + public function mapOneToMany(array $mapping): void + { + $mapping['type'] = self::ONE_TO_MANY; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Adds a many-to-one mapping. + * + * @psalm-param array $mapping The mapping. + */ + public function mapManyToOne(array $mapping): void + { + $mapping['type'] = self::MANY_TO_ONE; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Adds a many-to-many mapping. + * + * @psalm-param array $mapping The mapping. + */ + public function mapManyToMany(array $mapping): void + { + $mapping['type'] = self::MANY_TO_MANY; + + $mapping = $this->_validateAndCompleteAssociationMapping($mapping); + + $this->_storeAssociationMapping($mapping); + } + + /** + * Stores the association mapping. + * + * @param ConcreteAssociationMapping $assocMapping + * + * @throws MappingException + */ + protected function _storeAssociationMapping(AssociationMapping $assocMapping): void + { + $sourceFieldName = $assocMapping->fieldName; + + $this->assertFieldNotMapped($sourceFieldName); + + $this->associationMappings[$sourceFieldName] = $assocMapping; + } + + /** + * Registers a custom repository class for the entity class. + * + * @param string|null $repositoryClassName The class name of the custom mapper. + * @psalm-param class-string|null $repositoryClassName + */ + public function setCustomRepositoryClass(string|null $repositoryClassName): void + { + if ($repositoryClassName === null) { + $this->customRepositoryClassName = null; + + return; + } + + $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName); + } + + /** + * Dispatches the lifecycle event of the given entity to the registered + * lifecycle callbacks and lifecycle listeners. + * + * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker + * + * @param string $lifecycleEvent The lifecycle event. + */ + public function invokeLifecycleCallbacks(string $lifecycleEvent, object $entity): void + { + foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { + $entity->$callback(); + } + } + + /** + * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. + */ + public function hasLifecycleCallbacks(string $lifecycleEvent): bool + { + return isset($this->lifecycleCallbacks[$lifecycleEvent]); + } + + /** + * Gets the registered lifecycle callbacks for an event. + * + * @return string[] + * @psalm-return list + */ + public function getLifecycleCallbacks(string $event): array + { + return $this->lifecycleCallbacks[$event] ?? []; + } + + /** + * Adds a lifecycle callback for entities of this class. + */ + public function addLifecycleCallback(string $callback, string $event): void + { + if ($this->isEmbeddedClass) { + throw MappingException::illegalLifecycleCallbackOnEmbeddedClass($callback, $this->name); + } + + if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) { + return; + } + + $this->lifecycleCallbacks[$event][] = $callback; + } + + /** + * Sets the lifecycle callbacks for entities of this class. + * Any previously registered callbacks are overwritten. + * + * @psalm-param array> $callbacks + */ + public function setLifecycleCallbacks(array $callbacks): void + { + $this->lifecycleCallbacks = $callbacks; + } + + /** + * Adds a entity listener for entities of this class. + * + * @param string $eventName The entity lifecycle event. + * @param string $class The listener class. + * @param string $method The listener callback method. + * + * @throws MappingException + */ + public function addEntityListener(string $eventName, string $class, string $method): void + { + $class = $this->fullyQualifiedClassName($class); + + $listener = [ + 'class' => $class, + 'method' => $method, + ]; + + if (! class_exists($class)) { + throw MappingException::entityListenerClassNotFound($class, $this->name); + } + + if (! method_exists($class, $method)) { + throw MappingException::entityListenerMethodNotFound($class, $method, $this->name); + } + + if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) { + throw MappingException::duplicateEntityListener($class, $method, $this->name); + } + + $this->entityListeners[$eventName][] = $listener; + } + + /** + * Sets the discriminator column definition. + * + * @see getDiscriminatorColumn() + * + * @param DiscriminatorColumnMapping|mixed[]|null $columnDef + * @psalm-param DiscriminatorColumnMapping|array{ + * name: string|null, + * fieldName?: string|null, + * type?: string|null, + * length?: int|null, + * columnDefinition?: string|null, + * enumType?: class-string|null, + * options?: array|null + * }|null $columnDef + * + * @throws MappingException + */ + public function setDiscriminatorColumn(DiscriminatorColumnMapping|array|null $columnDef): void + { + if ($columnDef instanceof DiscriminatorColumnMapping) { + $this->discriminatorColumn = $columnDef; + + return; + } + + if ($columnDef !== null) { + if (! isset($columnDef['name'])) { + throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); + } + + if (isset($this->fieldNames[$columnDef['name']])) { + throw MappingException::duplicateColumnName($this->name, $columnDef['name']); + } + + $columnDef['fieldName'] ??= $columnDef['name']; + $columnDef['type'] ??= 'string'; + $columnDef['options'] ??= []; + + if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) { + throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); + } + + $this->discriminatorColumn = DiscriminatorColumnMapping::fromMappingArray($columnDef); + } + } + + final public function getDiscriminatorColumn(): DiscriminatorColumnMapping + { + if ($this->discriminatorColumn === null) { + throw new LogicException('The discriminator column was not set.'); + } + + return $this->discriminatorColumn; + } + + /** + * Sets the discriminator values used by this class. + * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. + * + * @param array $map + */ + public function setDiscriminatorMap(array $map): void + { + foreach ($map as $value => $className) { + $this->addDiscriminatorMapClass($value, $className); + } + } + + /** + * Adds one entry of the discriminator map with a new class and corresponding name. + * + * @throws MappingException + */ + public function addDiscriminatorMapClass(int|string $name, string $className): void + { + $className = $this->fullyQualifiedClassName($className); + $className = ltrim($className, '\\'); + + $this->discriminatorMap[$name] = $className; + + if ($this->name === $className) { + $this->discriminatorValue = $name; + + return; + } + + if (! (class_exists($className) || interface_exists($className))) { + throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); + } + + $this->addSubClass($className); + } + + /** @param array $classes */ + public function addSubClasses(array $classes): void + { + foreach ($classes as $className) { + $this->addSubClass($className); + } + } + + public function addSubClass(string $className): void + { + // By ignoring classes that are not subclasses of the current class, we simplify inheriting + // the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata. + + if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) { + $this->subClasses[] = $className; + } + } + + public function hasAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]); + } + + public function isSingleValuedAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]) + && ($this->associationMappings[$fieldName]->isToOne()); + } + + public function isCollectionValuedAssociation(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]) + && ! $this->associationMappings[$fieldName]->isToOne(); + } + + /** + * Is this an association that only has a single join column? + */ + public function isAssociationWithSingleJoinColumn(string $fieldName): bool + { + return isset($this->associationMappings[$fieldName]) + && isset($this->associationMappings[$fieldName]->joinColumns[0]) + && ! isset($this->associationMappings[$fieldName]->joinColumns[1]); + } + + /** + * Returns the single association join column (if any). + * + * @throws MappingException + */ + public function getSingleAssociationJoinColumnName(string $fieldName): string + { + if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { + throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); + } + + $assoc = $this->associationMappings[$fieldName]; + + assert($assoc->isToOneOwningSide()); + + return $assoc->joinColumns[0]->name; + } + + /** + * Returns the single association referenced join column name (if any). + * + * @throws MappingException + */ + public function getSingleAssociationReferencedJoinColumnName(string $fieldName): string + { + if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { + throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); + } + + $assoc = $this->associationMappings[$fieldName]; + + assert($assoc->isToOneOwningSide()); + + return $assoc->joinColumns[0]->referencedColumnName; + } + + /** + * Used to retrieve a fieldname for either field or association from a given column. + * + * This method is used in foreign-key as primary-key contexts. + * + * @throws MappingException + */ + public function getFieldForColumn(string $columnName): string + { + if (isset($this->fieldNames[$columnName])) { + return $this->fieldNames[$columnName]; + } + + foreach ($this->associationMappings as $assocName => $mapping) { + if ( + $this->isAssociationWithSingleJoinColumn($assocName) && + assert($this->associationMappings[$assocName]->isToOneOwningSide()) && + $this->associationMappings[$assocName]->joinColumns[0]->name === $columnName + ) { + return $assocName; + } + } + + throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); + } + + /** + * Sets the ID generator used to generate IDs for instances of this class. + */ + public function setIdGenerator(AbstractIdGenerator $generator): void + { + $this->idGenerator = $generator; + } + + /** + * Sets definition. + * + * @psalm-param array $definition + */ + public function setCustomGeneratorDefinition(array $definition): void + { + $this->customGeneratorDefinition = $definition; + } + + /** + * Sets the definition of the sequence ID generator for this class. + * + * The definition must have the following structure: + * + * array( + * 'sequenceName' => 'name', + * 'allocationSize' => 20, + * 'initialValue' => 1 + * 'quoted' => 1 + * ) + * + * + * @psalm-param array{sequenceName?: string, allocationSize?: int|string, initialValue?: int|string, quoted?: mixed} $definition + * + * @throws MappingException + */ + public function setSequenceGeneratorDefinition(array $definition): void + { + if (! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') { + throw MappingException::missingSequenceName($this->name); + } + + if ($definition['sequenceName'][0] === '`') { + $definition['sequenceName'] = trim($definition['sequenceName'], '`'); + $definition['quoted'] = true; + } + + if (! isset($definition['allocationSize']) || trim((string) $definition['allocationSize']) === '') { + $definition['allocationSize'] = '1'; + } + + if (! isset($definition['initialValue']) || trim((string) $definition['initialValue']) === '') { + $definition['initialValue'] = '1'; + } + + $definition['allocationSize'] = (string) $definition['allocationSize']; + $definition['initialValue'] = (string) $definition['initialValue']; + + $this->sequenceGeneratorDefinition = $definition; + } + + /** + * Sets the version field mapping used for versioning. Sets the default + * value to use depending on the column type. + * + * @psalm-param array $mapping The version field mapping array. + * + * @throws MappingException + */ + public function setVersionMapping(array &$mapping): void + { + $this->isVersioned = true; + $this->versionField = $mapping['fieldName']; + $this->requiresFetchAfterChange = true; + + if (! isset($mapping['default'])) { + if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) { + $mapping['default'] = 1; + } elseif ($mapping['type'] === 'datetime') { + $mapping['default'] = 'CURRENT_TIMESTAMP'; + } else { + throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']); + } + } + } + + /** + * Sets whether this class is to be versioned for optimistic locking. + */ + public function setVersioned(bool $bool): void + { + $this->isVersioned = $bool; + + if ($bool) { + $this->requiresFetchAfterChange = true; + } + } + + /** + * Sets the name of the field that is to be used for versioning if this class is + * versioned for optimistic locking. + */ + public function setVersionField(string|null $versionField): void + { + $this->versionField = $versionField; + } + + /** + * Marks this class as read only, no change tracking is applied to it. + */ + public function markReadOnly(): void + { + $this->isReadOnly = true; + } + + /** + * {@inheritDoc} + */ + public function getFieldNames(): array + { + return array_keys($this->fieldMappings); + } + + /** + * {@inheritDoc} + */ + public function getAssociationNames(): array + { + return array_keys($this->associationMappings); + } + + /** + * {@inheritDoc} + * + * @psalm-return class-string + * + * @throws InvalidArgumentException + */ + public function getAssociationTargetClass(string $assocName): string + { + return $this->associationMappings[$assocName]->targetEntity + ?? throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association."); + } + + public function getName(): string + { + return $this->name; + } + + public function isAssociationInverseSide(string $assocName): bool + { + return isset($this->associationMappings[$assocName]) + && ! $this->associationMappings[$assocName]->isOwningSide(); + } + + public function getAssociationMappedByTargetField(string $assocName): string + { + $assoc = $this->getAssociationMapping($assocName); + + if (! $assoc instanceof InverseSideMapping) { + throw new LogicException(sprintf( + <<<'EXCEPTION' + Context: Calling %s() with "%s", which is the owning side of an association. + Problem: The owning side of an association has no "mappedBy" field. + Solution: Call %s::isAssociationInverseSide() to check first. + EXCEPTION, + __METHOD__, + $assocName, + self::class, + )); + } + + return $assoc->mappedBy; + } + + /** + * @param C $className + * + * @return string|null null if and only if the input value is null + * @psalm-return (C is class-string ? class-string : (C is string ? string : null)) + * + * @template C of string|null + */ + public function fullyQualifiedClassName(string|null $className): string|null + { + if ($className === null) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11294', + 'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0', + __METHOD__, + ); + + return null; + } + + if (! str_contains($className, '\\') && $this->namespace) { + return $this->namespace . '\\' . $className; + } + + return $className; + } + + public function getMetadataValue(string $name): mixed + { + return $this->$name ?? null; + } + + /** + * Map Embedded Class + * + * @psalm-param array{ + * fieldName: string, + * class?: class-string, + * declaredField?: string, + * columnPrefix?: string|false|null, + * originalField?: string + * } $mapping + * + * @throws MappingException + */ + public function mapEmbedded(array $mapping): void + { + $this->assertFieldNotMapped($mapping['fieldName']); + + if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) { + $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); + if ($type instanceof ReflectionNamedType) { + $mapping['class'] = $type->getName(); + } + } + + if (! (isset($mapping['class']) && $mapping['class'])) { + throw MappingException::missingEmbeddedClass($mapping['fieldName']); + } + + $this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([ + 'class' => $this->fullyQualifiedClassName($mapping['class']), + 'columnPrefix' => $mapping['columnPrefix'] ?? null, + 'declaredField' => $mapping['declaredField'] ?? null, + 'originalField' => $mapping['originalField'] ?? null, + ]); + } + + /** + * Inline the embeddable class + */ + public function inlineEmbeddable(string $property, ClassMetadata $embeddable): void + { + foreach ($embeddable->fieldMappings as $originalFieldMapping) { + $fieldMapping = (array) $originalFieldMapping; + $fieldMapping['originalClass'] ??= $embeddable->name; + $fieldMapping['declaredField'] = isset($fieldMapping['declaredField']) + ? $property . '.' . $fieldMapping['declaredField'] + : $property; + $fieldMapping['originalField'] ??= $fieldMapping['fieldName']; + $fieldMapping['fieldName'] = $property . '.' . $fieldMapping['fieldName']; + + if (! empty($this->embeddedClasses[$property]->columnPrefix)) { + $fieldMapping['columnName'] = $this->embeddedClasses[$property]->columnPrefix . $fieldMapping['columnName']; + } elseif ($this->embeddedClasses[$property]->columnPrefix !== false) { + assert($this->reflClass !== null); + assert($embeddable->reflClass !== null); + $fieldMapping['columnName'] = $this->namingStrategy + ->embeddedFieldToColumnName( + $property, + $fieldMapping['columnName'], + $this->reflClass->name, + $embeddable->reflClass->name, + ); + } + + $this->mapField($fieldMapping); + } + } + + /** @throws MappingException */ + private function assertFieldNotMapped(string $fieldName): void + { + if ( + isset($this->fieldMappings[$fieldName]) || + isset($this->associationMappings[$fieldName]) || + isset($this->embeddedClasses[$fieldName]) + ) { + throw MappingException::duplicateFieldMapping($this->name, $fieldName); + } + } + + /** + * Gets the sequence name based on class metadata. + * + * @todo Sequence names should be computed in DBAL depending on the platform + */ + public function getSequenceName(AbstractPlatform $platform): string + { + $sequencePrefix = $this->getSequencePrefix($platform); + $columnName = $this->getSingleIdentifierColumnName(); + + return $sequencePrefix . '_' . $columnName . '_seq'; + } + + /** + * Gets the sequence name prefix based on class metadata. + * + * @todo Sequence names should be computed in DBAL depending on the platform + */ + public function getSequencePrefix(AbstractPlatform $platform): string + { + $tableName = $this->getTableName(); + $sequencePrefix = $tableName; + + // Prepend the schema name to the table name if there is one + $schemaName = $this->getSchemaName(); + if ($schemaName) { + $sequencePrefix = $schemaName . '.' . $tableName; + } + + return $sequencePrefix; + } + + /** @psalm-param class-string $class */ + private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ReflectionProperty|null + { + $reflectionProperty = $reflService->getAccessibleProperty($class, $field); + if ($reflectionProperty?->isReadOnly()) { + $declaringClass = $reflectionProperty->class; + if ($declaringClass !== $class) { + $reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field); + } + + if ($reflectionProperty !== null) { + $reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty); + } + } + + return $reflectionProperty; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php b/vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php new file mode 100644 index 0000000..a3a4701 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php @@ -0,0 +1,729 @@ + + */ +class ClassMetadataFactory extends AbstractClassMetadataFactory +{ + private EntityManagerInterface|null $em = null; + private AbstractPlatform|null $targetPlatform = null; + private MappingDriver|null $driver = null; + private EventManager|null $evm = null; + + /** @var mixed[] */ + private array $embeddablesActiveNesting = []; + + private const NON_IDENTITY_DEFAULT_STRATEGY = [ + Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE, + ]; + + public function setEntityManager(EntityManagerInterface $em): void + { + parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver()); + + $this->em = $em; + } + + /** + * @param A $maybeOwningSide + * + * @return (A is ManyToManyAssociationMapping ? ManyToManyOwningSideMapping : ( + * A is OneToOneAssociationMapping ? OneToOneOwningSideMapping : ( + * A is OneToManyAssociationMapping ? ManyToOneAssociationMapping : ( + * A is ManyToOneAssociationMapping ? ManyToOneAssociationMapping : + * ManyToManyOwningSideMapping|OneToOneOwningSideMapping|ManyToOneAssociationMapping + * )))) + * + * @template A of AssociationMapping + */ + final public function getOwningSide(AssociationMapping $maybeOwningSide): OwningSideMapping + { + if ($maybeOwningSide instanceof OwningSideMapping) { + assert($maybeOwningSide instanceof ManyToManyOwningSideMapping || + $maybeOwningSide instanceof OneToOneOwningSideMapping || + $maybeOwningSide instanceof ManyToOneAssociationMapping); + + return $maybeOwningSide; + } + + assert($maybeOwningSide instanceof InverseSideMapping); + + $owningSide = $this->getMetadataFor($maybeOwningSide->targetEntity) + ->associationMappings[$maybeOwningSide->mappedBy]; + + assert($owningSide instanceof ManyToManyOwningSideMapping || + $owningSide instanceof OneToOneOwningSideMapping || + $owningSide instanceof ManyToOneAssociationMapping); + + return $owningSide; + } + + protected function initialize(): void + { + $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); + $this->evm = $this->em->getEventManager(); + $this->initialized = true; + } + + protected function onNotFoundMetadata(string $className): ClassMetadata|null + { + if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) { + return null; + } + + $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em); + + $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs); + $classMetadata = $eventArgs->getFoundMetadata(); + assert($classMetadata instanceof ClassMetadata || $classMetadata === null); + + return $classMetadata; + } + + /** + * {@inheritDoc} + */ + protected function doLoadMetadata( + ClassMetadataInterface $class, + ClassMetadataInterface|null $parent, + bool $rootEntityFound, + array $nonSuperclassParents, + ): void { + if ($parent) { + $class->setInheritanceType($parent->inheritanceType); + $class->setDiscriminatorColumn($parent->discriminatorColumn === null ? null : clone $parent->discriminatorColumn); + $class->setIdGeneratorType($parent->generatorType); + $this->addInheritedFields($class, $parent); + $this->addInheritedRelations($class, $parent); + $this->addInheritedEmbeddedClasses($class, $parent); + $class->setIdentifier($parent->identifier); + $class->setVersioned($parent->isVersioned); + $class->setVersionField($parent->versionField); + $class->setDiscriminatorMap($parent->discriminatorMap); + $class->addSubClasses($parent->subClasses); + $class->setLifecycleCallbacks($parent->lifecycleCallbacks); + $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); + + if (! empty($parent->customGeneratorDefinition)) { + $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition); + } + + if ($parent->isMappedSuperclass) { + $class->setCustomRepositoryClass($parent->customRepositoryClassName); + } + } + + // Invoke driver + try { + $this->driver->loadMetadataForClass($class->getName(), $class); + } catch (ReflectionException $e) { + throw MappingException::reflectionFailure($class->getName(), $e); + } + + // If this class has a parent the id generator strategy is inherited. + // However this is only true if the hierarchy of parents contains the root entity, + // if it consists of mapped superclasses these don't necessarily include the id field. + if ($parent && $rootEntityFound) { + $this->inheritIdGeneratorMapping($class, $parent); + } else { + $this->completeIdGeneratorMapping($class); + } + + if (! $class->isMappedSuperclass) { + if ($rootEntityFound && $class->isInheritanceTypeNone()) { + throw MappingException::missingInheritanceTypeDeclaration(end($nonSuperclassParents), $class->name); + } + + foreach ($class->embeddedClasses as $property => $embeddableClass) { + if (isset($embeddableClass->inherited)) { + continue; + } + + if (isset($this->embeddablesActiveNesting[$embeddableClass->class])) { + throw MappingException::infiniteEmbeddableNesting($class->name, $property); + } + + $this->embeddablesActiveNesting[$class->name] = true; + + $embeddableMetadata = $this->getMetadataFor($embeddableClass->class); + + if ($embeddableMetadata->isEmbeddedClass) { + $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property); + } + + $identifier = $embeddableMetadata->getIdentifier(); + + if (! empty($identifier)) { + $this->inheritIdGeneratorMapping($class, $embeddableMetadata); + } + + $class->inlineEmbeddable($property, $embeddableMetadata); + + unset($this->embeddablesActiveNesting[$class->name]); + } + } + + if ($parent) { + if ($parent->isInheritanceTypeSingleTable()) { + $class->setPrimaryTable($parent->table); + } + + $this->addInheritedIndexes($class, $parent); + + if ($parent->cache) { + $class->cache = $parent->cache; + } + + if ($parent->containsForeignIdentifier) { + $class->containsForeignIdentifier = true; + } + + if ($parent->containsEnumIdentifier) { + $class->containsEnumIdentifier = true; + } + + if (! empty($parent->entityListeners) && empty($class->entityListeners)) { + $class->entityListeners = $parent->entityListeners; + } + } + + $class->setParentClasses($nonSuperclassParents); + + if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { + $this->addDefaultDiscriminatorMap($class); + } + + // During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402. + // So, we must not discover the missing subclasses before that. + + if ($this->evm->hasListeners(Events::loadClassMetadata)) { + $eventArgs = new LoadClassMetadataEventArgs($class, $this->em); + $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); + } + + $this->findAbstractEntityClassesNotListedInDiscriminatorMap($class); + + $this->validateRuntimeMetadata($class, $parent); + } + + /** + * Validate runtime metadata is correctly defined. + * + * @throws MappingException + */ + protected function validateRuntimeMetadata(ClassMetadata $class, ClassMetadataInterface|null $parent): void + { + if (! $class->reflClass) { + // only validate if there is a reflection class instance + return; + } + + $class->validateIdentifier(); + $class->validateAssociations(); + $class->validateLifecycleCallbacks($this->getReflectionService()); + + // verify inheritance + if (! $class->isMappedSuperclass && ! $class->isInheritanceTypeNone()) { + if (! $parent) { + if (count($class->discriminatorMap) === 0) { + throw MappingException::missingDiscriminatorMap($class->name); + } + + if (! $class->discriminatorColumn) { + throw MappingException::missingDiscriminatorColumn($class->name); + } + + foreach ($class->subClasses as $subClass) { + if ((new ReflectionClass($subClass))->name !== $subClass) { + throw MappingException::invalidClassInDiscriminatorMap($subClass, $class->name); + } + } + } else { + assert($parent instanceof ClassMetadata); // https://github.com/doctrine/orm/issues/8746 + if ( + ! $class->reflClass->isAbstract() + && ! in_array($class->name, $class->discriminatorMap, true) + ) { + throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); + } + } + } elseif ($class->isMappedSuperclass && $class->name === $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { + // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy + throw MappingException::noInheritanceOnMappedSuperClass($class->name); + } + } + + protected function newClassMetadataInstance(string $className): ClassMetadata + { + return new ClassMetadata( + $className, + $this->em->getConfiguration()->getNamingStrategy(), + $this->em->getConfiguration()->getTypedFieldMapper(), + ); + } + + /** + * Adds a default discriminator map if no one is given + * + * If an entity is of any inheritance type and does not contain a + * discriminator map, then the map is generated automatically. This process + * is expensive computation wise. + * + * The automatically generated discriminator map contains the lowercase short name of + * each class as key. + * + * @throws MappingException + */ + private function addDefaultDiscriminatorMap(ClassMetadata $class): void + { + $allClasses = $this->driver->getAllClassNames(); + $fqcn = $class->getName(); + $map = [$this->getShortName($class->name) => $fqcn]; + + $duplicates = []; + foreach ($allClasses as $subClassCandidate) { + if (is_subclass_of($subClassCandidate, $fqcn)) { + $shortName = $this->getShortName($subClassCandidate); + + if (isset($map[$shortName])) { + $duplicates[] = $shortName; + } + + $map[$shortName] = $subClassCandidate; + } + } + + if ($duplicates) { + throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); + } + + $class->setDiscriminatorMap($map); + } + + private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void + { + // Only root classes in inheritance hierarchies need contain a discriminator map, + // so skip for other classes. + if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) { + return; + } + + $processedClasses = [$rootEntityClass->name => true]; + foreach ($rootEntityClass->subClasses as $knownSubClass) { + $processedClasses[$knownSubClass] = true; + } + + foreach ($rootEntityClass->discriminatorMap as $declaredClassName) { + // This fetches non-transient parent classes only + $parentClasses = $this->getParentClasses($declaredClassName); + + foreach ($parentClasses as $parentClass) { + if (isset($processedClasses[$parentClass])) { + continue; + } + + $processedClasses[$parentClass] = true; + + // All non-abstract entity classes must be listed in the discriminator map, and + // this will be validated/enforced at runtime (possibly at a later time, when the + // subclass is loaded, but anyways). Also, subclasses is about entity classes only. + // That means we can ignore non-abstract classes here. The (expensive) driver + // check for mapped superclasses need only be run for abstract candidate classes. + if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) { + continue; + } + + // We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter) + $rootEntityClass->addSubClass($parentClass); + } + } + } + + /** @param class-string $className */ + private function peekIfIsMappedSuperclass(string $className): bool + { + $reflService = $this->getReflectionService(); + $class = $this->newClassMetadataInstance($className); + $this->initializeReflection($class, $reflService); + + $this->getDriver()->loadMetadataForClass($className, $class); + + return $class->isMappedSuperclass; + } + + /** + * Gets the lower-case short name of a class. + * + * @psalm-param class-string $className + */ + private function getShortName(string $className): string + { + if (! str_contains($className, '\\')) { + return strtolower($className); + } + + $parts = explode('\\', $className); + + return strtolower(end($parts)); + } + + /** + * Puts the `inherited` and `declared` values into mapping information for fields, associations + * and embedded classes. + */ + private function addMappingInheritanceInformation( + AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping, + ClassMetadata $parentClass, + ): void { + if (! isset($mapping->inherited) && ! $parentClass->isMappedSuperclass) { + $mapping->inherited = $parentClass->name; + } + + if (! isset($mapping->declared)) { + $mapping->declared = $parentClass->name; + } + } + + /** + * Adds inherited fields to the subclass mapping. + */ + private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + foreach ($parentClass->fieldMappings as $mapping) { + $subClassMapping = clone $mapping; + $this->addMappingInheritanceInformation($subClassMapping, $parentClass); + $subClass->addInheritedFieldMapping($subClassMapping); + } + + foreach ($parentClass->reflFields as $name => $field) { + $subClass->reflFields[$name] = $field; + } + } + + /** + * Adds inherited association mappings to the subclass mapping. + * + * @throws MappingException + */ + private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + foreach ($parentClass->associationMappings as $field => $mapping) { + $subClassMapping = clone $mapping; + $this->addMappingInheritanceInformation($subClassMapping, $parentClass); + // When the class inheriting the relation ($subClass) is the first entity class since the + // relation has been defined in a mapped superclass (or in a chain + // of mapped superclasses) above, then declare this current entity class as the source of + // the relationship. + // According to the definitions given in https://github.com/doctrine/orm/pull/10396/, + // this is the case <=> ! isset($mapping['inherited']). + if (! isset($subClassMapping->inherited)) { + $subClassMapping->sourceEntity = $subClass->name; + } + + $subClass->addInheritedAssociationMapping($subClassMapping); + } + } + + private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + foreach ($parentClass->embeddedClasses as $field => $embeddedClass) { + $subClassMapping = clone $embeddedClass; + $this->addMappingInheritanceInformation($subClassMapping, $parentClass); + $subClass->embeddedClasses[$field] = $subClassMapping; + } + } + + /** + * Adds nested embedded classes metadata to a parent class. + * + * @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from. + * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to. + * @param string $prefix Embedded classes' prefix to use for nested embedded classes field names. + */ + private function addNestedEmbeddedClasses( + ClassMetadata $subClass, + ClassMetadata $parentClass, + string $prefix, + ): void { + foreach ($subClass->embeddedClasses as $property => $embeddableClass) { + if (isset($embeddableClass->inherited)) { + continue; + } + + $embeddableMetadata = $this->getMetadataFor($embeddableClass->class); + + $parentClass->mapEmbedded( + [ + 'fieldName' => $prefix . '.' . $property, + 'class' => $embeddableMetadata->name, + 'columnPrefix' => $embeddableClass->columnPrefix, + 'declaredField' => $embeddableClass->declaredField + ? $prefix . '.' . $embeddableClass->declaredField + : $prefix, + 'originalField' => $embeddableClass->originalField ?: $property, + ], + ); + } + } + + /** + * Copy the table indices from the parent class superclass to the child class + */ + private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void + { + if (! $parentClass->isMappedSuperclass) { + return; + } + + foreach (['uniqueConstraints', 'indexes'] as $indexType) { + if (isset($parentClass->table[$indexType])) { + foreach ($parentClass->table[$indexType] as $indexName => $index) { + if (isset($subClass->table[$indexType][$indexName])) { + continue; // Let the inheriting table override indices + } + + $subClass->table[$indexType][$indexName] = $index; + } + } + } + } + + /** + * Completes the ID generator mapping. If "auto" is specified we choose the generator + * most appropriate for the targeted database platform. + * + * @throws ORMException + */ + private function completeIdGeneratorMapping(ClassMetadata $class): void + { + $idGenType = $class->generatorType; + if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) { + $class->setIdGeneratorType($this->determineIdGeneratorStrategy($this->getTargetPlatform())); + } + + // Create & assign an appropriate ID generator instance + switch ($class->generatorType) { + case ClassMetadata::GENERATOR_TYPE_IDENTITY: + $sequenceName = null; + $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null; + $platform = $this->getTargetPlatform(); + + $generator = $fieldName && $class->fieldMappings[$fieldName]->type === 'bigint' + ? new BigIntegerIdentityGenerator() + : new IdentityGenerator(); + + $class->setIdGenerator($generator); + + break; + + case ClassMetadata::GENERATOR_TYPE_SEQUENCE: + // If there is no sequence definition yet, create a default definition + $definition = $class->sequenceGeneratorDefinition; + + if (! $definition) { + $fieldName = $class->getSingleIdentifierFieldName(); + $sequenceName = $class->getSequenceName($this->getTargetPlatform()); + $quoted = isset($class->fieldMappings[$fieldName]->quoted) || isset($class->table['quoted']); + + $definition = [ + 'sequenceName' => $this->truncateSequenceName($sequenceName), + 'allocationSize' => 1, + 'initialValue' => 1, + ]; + + if ($quoted) { + $definition['quoted'] = true; + } + + $class->setSequenceGeneratorDefinition($definition); + } + + $sequenceGenerator = new SequenceGenerator( + $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()), + (int) $definition['allocationSize'], + ); + $class->setIdGenerator($sequenceGenerator); + break; + + case ClassMetadata::GENERATOR_TYPE_NONE: + $class->setIdGenerator(new AssignedGenerator()); + break; + + case ClassMetadata::GENERATOR_TYPE_CUSTOM: + $definition = $class->customGeneratorDefinition; + if ($definition === null) { + throw InvalidCustomGenerator::onClassNotConfigured(); + } + + if (! class_exists($definition['class'])) { + throw InvalidCustomGenerator::onMissingClass($definition); + } + + $class->setIdGenerator(new $definition['class']()); + break; + + default: + throw UnknownGeneratorType::create($class->generatorType); + } + } + + /** @psalm-return ClassMetadata::GENERATOR_TYPE_* */ + private function determineIdGeneratorStrategy(AbstractPlatform $platform): int + { + assert($this->em !== null); + foreach ($this->em->getConfiguration()->getIdentityGenerationPreferences() as $platformFamily => $strategy) { + if (is_a($platform, $platformFamily)) { + return $strategy; + } + } + + $nonIdentityDefaultStrategy = self::NON_IDENTITY_DEFAULT_STRATEGY; + + // DBAL 3 + if (method_exists($platform, 'getIdentitySequenceName')) { + $nonIdentityDefaultStrategy[Platforms\PostgreSQLPlatform::class] = ClassMetadata::GENERATOR_TYPE_SEQUENCE; + } + + foreach ($nonIdentityDefaultStrategy as $platformFamily => $strategy) { + if (is_a($platform, $platformFamily)) { + if ($platform instanceof Platforms\PostgreSQLPlatform) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/issues/8893', + <<<'DEPRECATION' + Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY + results in SERIAL, which is not recommended. + Instead, configure identifier generation strategies explicitly through + configuration. + We currently recommend "SEQUENCE" for "%s", when using DBAL 3, + and "IDENTITY" when using DBAL 4, + so you should probably use the following configuration before upgrading to DBAL 4, + and remove it after deploying that upgrade: + + $configuration->setIdentityGenerationPreferences([ + "%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE, + ]); + + DEPRECATION, + $platformFamily, + $platformFamily, + ); + } + + return $strategy; + } + } + + return ClassMetadata::GENERATOR_TYPE_IDENTITY; + } + + private function truncateSequenceName(string $schemaElementName): string + { + $platform = $this->getTargetPlatform(); + if (! $platform instanceof Platforms\OraclePlatform) { + return $schemaElementName; + } + + $maxIdentifierLength = $platform->getMaxIdentifierLength(); + + if (strlen($schemaElementName) > $maxIdentifierLength) { + return substr($schemaElementName, 0, $maxIdentifierLength); + } + + return $schemaElementName; + } + + /** + * Inherits the ID generator mapping from a parent class. + */ + private function inheritIdGeneratorMapping(ClassMetadata $class, ClassMetadata $parent): void + { + if ($parent->isIdGeneratorSequence()) { + $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); + } + + if ($parent->generatorType) { + $class->setIdGeneratorType($parent->generatorType); + } + + if ($parent->idGenerator ?? null) { + $class->setIdGenerator($parent->idGenerator); + } + } + + protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService): void + { + $class->wakeupReflection($reflService); + } + + protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService): void + { + $class->initializeReflection($reflService); + } + + protected function getDriver(): MappingDriver + { + assert($this->driver !== null); + + return $this->driver; + } + + protected function isEntity(ClassMetadataInterface $class): bool + { + return ! $class->isMappedSuperclass; + } + + private function getTargetPlatform(): Platforms\AbstractPlatform + { + if (! $this->targetPlatform) { + $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); + } + + return $this->targetPlatform; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Column.php b/vendor/doctrine/orm/src/Mapping/Column.php new file mode 100644 index 0000000..68121e6 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Column.php @@ -0,0 +1,36 @@ +|null $enumType + * @param array $options + * @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated + */ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $type = null, + public readonly int|null $length = null, + public readonly int|null $precision = null, + public readonly int|null $scale = null, + public readonly bool $unique = false, + public readonly bool $nullable = false, + public readonly bool $insertable = true, + public readonly bool $updatable = true, + public readonly string|null $enumType = null, + public readonly array $options = [], + public readonly string|null $columnDefinition = null, + public readonly string|null $generated = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php b/vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php new file mode 100644 index 0000000..7b31dc3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/CustomIdGenerator.php @@ -0,0 +1,16 @@ + Map to store entity listener instances. */ + private array $instances = []; + + public function clear(string|null $className = null): void + { + if ($className === null) { + $this->instances = []; + + return; + } + + $className = trim($className, '\\'); + unset($this->instances[$className]); + } + + public function register(object $object): void + { + $this->instances[$object::class] = $object; + } + + public function resolve(string $className): object + { + $className = trim($className, '\\'); + + return $this->instances[$className] ??= new $className(); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php b/vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php new file mode 100644 index 0000000..15218f9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultNamingStrategy.php @@ -0,0 +1,68 @@ +referenceColumnName(); + } + + public function joinTableName( + string $sourceEntity, + string $targetEntity, + string $propertyName, + ): string { + return strtolower($this->classToTableName($sourceEntity) . '_' . + $this->classToTableName($targetEntity)); + } + + public function joinKeyColumnName( + string $entityName, + string|null $referencedColumnName, + ): string { + return strtolower($this->classToTableName($entityName) . '_' . + ($referencedColumnName ?: $this->referenceColumnName())); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php b/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php new file mode 100644 index 0000000..6260336 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultQuoteStrategy.php @@ -0,0 +1,145 @@ +fieldMappings[$fieldName]->quoted) + ? $platform->quoteIdentifier($class->fieldMappings[$fieldName]->columnName) + : $class->fieldMappings[$fieldName]->columnName; + } + + /** + * {@inheritDoc} + * + * @todo Table names should be computed in DBAL depending on the platform + */ + public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string + { + $tableName = $class->table['name']; + + if (! empty($class->table['schema'])) { + $tableName = $class->table['schema'] . '.' . $class->table['name']; + } + + return isset($class->table['quoted']) + ? $platform->quoteIdentifier($tableName) + : $tableName; + } + + /** + * {@inheritDoc} + */ + public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string + { + return isset($definition['quoted']) + ? $platform->quoteIdentifier($definition['sequenceName']) + : $definition['sequenceName']; + } + + public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string + { + return isset($joinColumn->quoted) + ? $platform->quoteIdentifier($joinColumn->name) + : $joinColumn->name; + } + + public function getReferencedJoinColumnName( + JoinColumnMapping $joinColumn, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + return isset($joinColumn->quoted) + ? $platform->quoteIdentifier($joinColumn->referencedColumnName) + : $joinColumn->referencedColumnName; + } + + public function getJoinTableName( + ManyToManyOwningSideMapping $association, + ClassMetadata $class, + AbstractPlatform $platform, + ): string { + $schema = ''; + + if (isset($association->joinTable->schema)) { + $schema = $association->joinTable->schema . '.'; + } + + $tableName = $association->joinTable->name; + + if (isset($association->joinTable->quoted)) { + $tableName = $platform->quoteIdentifier($tableName); + } + + return $schema . $tableName; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array + { + $quotedColumnNames = []; + + foreach ($class->identifier as $fieldName) { + if (isset($class->fieldMappings[$fieldName])) { + $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform); + + continue; + } + + // Association defined as Id field + $assoc = $class->associationMappings[$fieldName]; + assert($assoc->isToOneOwningSide()); + $joinColumns = $assoc->joinColumns; + $assocQuotedColumnNames = array_map( + static fn (JoinColumnMapping $joinColumn) => isset($joinColumn->quoted) + ? $platform->quoteIdentifier($joinColumn->name) + : $joinColumn->name, + $joinColumns, + ); + + $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); + } + + return $quotedColumnNames; + } + + public function getColumnAlias( + string $columnName, + int $counter, + AbstractPlatform $platform, + ClassMetadata|null $class = null, + ): string { + // 1 ) Concatenate column name and counter + // 2 ) Trim the column alias to the maximum identifier length of the platform. + // If the alias is to long, characters are cut off from the beginning. + // 3 ) Strip non alphanumeric characters + // 4 ) Prefix with "_" if the result its numeric + $columnName .= '_' . $counter; + $columnName = substr($columnName, -$platform->getMaxIdentifierLength()); + $columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName); + $columnName = is_numeric($columnName) ? '_' . $columnName : $columnName; + + return $this->getSQLResultCasing($platform, $columnName); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php b/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php new file mode 100644 index 0000000..49144b8 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultTypedFieldMapper.php @@ -0,0 +1,80 @@ +|string> $typedFieldMappings */ + private array $typedFieldMappings; + + private const DEFAULT_TYPED_FIELD_MAPPINGS = [ + DateInterval::class => Types::DATEINTERVAL, + DateTime::class => Types::DATETIME_MUTABLE, + DateTimeImmutable::class => Types::DATETIME_IMMUTABLE, + 'array' => Types::JSON, + 'bool' => Types::BOOLEAN, + 'float' => Types::FLOAT, + 'int' => Types::INTEGER, + 'string' => Types::STRING, + ]; + + /** @param array|string> $typedFieldMappings */ + public function __construct(array $typedFieldMappings = []) + { + $this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings); + } + + /** + * {@inheritDoc} + */ + public function validateAndComplete(array $mapping, ReflectionProperty $field): array + { + $type = $field->getType(); + + if ( + ! isset($mapping['type']) + && ($type instanceof ReflectionNamedType) + ) { + if (! $type->isBuiltin() && enum_exists($type->getName())) { + $reflection = new ReflectionEnum($type->getName()); + if (! $reflection->isBacked()) { + throw MappingException::backedEnumTypeRequired( + $field->class, + $mapping['fieldName'], + $type->getName(), + ); + } + + assert(is_a($type->getName(), BackedEnum::class, true)); + $mapping['enumType'] = $type->getName(); + $type = $reflection->getBackingType(); + + assert($type instanceof ReflectionNamedType); + } + + if (isset($this->typedFieldMappings[$type->getName()])) { + $mapping['type'] = $this->typedFieldMappings[$type->getName()]; + } + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php new file mode 100644 index 0000000..fb9c7d3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumn.php @@ -0,0 +1,24 @@ +|null */ + public readonly string|null $enumType = null, + /** @var array */ + public readonly array $options = [], + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php new file mode 100644 index 0000000..4ccb71c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DiscriminatorColumnMapping.php @@ -0,0 +1,83 @@ + */ +final class DiscriminatorColumnMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + /** The database length of the column. Optional. Default value taken from the type. */ + public int|null $length = null; + + public string|null $columnDefinition = null; + + /** @var class-string|null */ + public string|null $enumType = null; + + /** @var array */ + public array $options = []; + + public function __construct( + public string $type, + public string $fieldName, + public string $name, + ) { + } + + /** + * @psalm-param array{ + * type: string, + * fieldName: string, + * name: string, + * length?: int|null, + * columnDefinition?: string|null, + * enumType?: class-string|null, + * options?: array|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self( + $mappingArray['type'], + $mappingArray['fieldName'], + $mappingArray['name'], + ); + foreach ($mappingArray as $key => $value) { + if (in_array($key, ['type', 'fieldName', 'name'])) { + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value ?? $mapping->$key; + } else { + throw new Exception('Unknown property ' . $key . ' on class ' . static::class); + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['type', 'fieldName', 'name']; + + foreach (['length', 'columnDefinition', 'enumType', 'options'] as $stringOrArrayKey) { + if ($this->$stringOrArrayKey !== null) { + $serialized[] = $stringOrArrayKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php b/vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php new file mode 100644 index 0000000..2b204a9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DiscriminatorMap.php @@ -0,0 +1,17 @@ + $value */ + public function __construct( + public readonly array $value, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php new file mode 100644 index 0000000..6fed1a2 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php @@ -0,0 +1,768 @@ + 1, + Mapping\MappedSuperclass::class => 2, + ]; + + private readonly AttributeReader $reader; + + /** + * @param array $paths + * @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0 + */ + public function __construct(array $paths, bool $reportFieldsWhereDeclared = true) + { + if (! $reportFieldsWhereDeclared) { + throw new InvalidArgumentException(sprintf( + 'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.', + __METHOD__, + )); + } + + $this->reader = new AttributeReader(); + $this->addPaths($paths); + } + + public function isTransient(string $className): bool + { + $classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className)); + + foreach ($classAttributes as $a) { + $attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a; + if (isset(self::ENTITY_ATTRIBUTE_CLASSES[$attr::class])) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + * + * @psalm-param class-string $className + * @psalm-param ClassMetadata $metadata + * + * @template T of object + */ + public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void + { + $reflectionClass = $metadata->getReflectionClass() + // this happens when running attribute driver in combination with + // static reflection services. This is not the nicest fix + ?? new ReflectionClass($metadata->name); + + $classAttributes = $this->reader->getClassAttributes($reflectionClass); + + // Evaluate Entity attribute + if (isset($classAttributes[Mapping\Entity::class])) { + $entityAttribute = $classAttributes[Mapping\Entity::class]; + if ($entityAttribute->repositoryClass !== null) { + $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass); + } + + if ($entityAttribute->readOnly) { + $metadata->markReadOnly(); + } + } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) { + $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class]; + + $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass); + $metadata->isMappedSuperclass = true; + } elseif (isset($classAttributes[Mapping\Embeddable::class])) { + $metadata->isEmbeddedClass = true; + } else { + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); + } + + $primaryTable = []; + + if (isset($classAttributes[Mapping\Table::class])) { + $tableAnnot = $classAttributes[Mapping\Table::class]; + $primaryTable['name'] = $tableAnnot->name; + $primaryTable['schema'] = $tableAnnot->schema; + + if ($tableAnnot->options) { + $primaryTable['options'] = $tableAnnot->options; + } + } + + if (isset($classAttributes[Mapping\Index::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Index::class); + } + + foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) { + $index = []; + + if (! empty($indexAnnot->columns)) { + $index['columns'] = $indexAnnot->columns; + } + + if (! empty($indexAnnot->fields)) { + $index['fields'] = $indexAnnot->fields; + } + + if ( + isset($index['columns'], $index['fields']) + || ( + ! isset($index['columns']) + && ! isset($index['fields']) + ) + ) { + throw MappingException::invalidIndexConfiguration( + $className, + (string) ($indexAnnot->name ?? $idx), + ); + } + + if (! empty($indexAnnot->flags)) { + $index['flags'] = $indexAnnot->flags; + } + + if (! empty($indexAnnot->options)) { + $index['options'] = $indexAnnot->options; + } + + if (! empty($indexAnnot->name)) { + $primaryTable['indexes'][$indexAnnot->name] = $index; + } else { + $primaryTable['indexes'][] = $index; + } + } + } + + if (isset($classAttributes[Mapping\UniqueConstraint::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\UniqueConstraint::class); + } + + foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) { + $uniqueConstraint = []; + + if (! empty($uniqueConstraintAnnot->columns)) { + $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns; + } + + if (! empty($uniqueConstraintAnnot->fields)) { + $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields; + } + + if ( + isset($uniqueConstraint['columns'], $uniqueConstraint['fields']) + || ( + ! isset($uniqueConstraint['columns']) + && ! isset($uniqueConstraint['fields']) + ) + ) { + throw MappingException::invalidUniqueConstraintConfiguration( + $className, + (string) ($uniqueConstraintAnnot->name ?? $idx), + ); + } + + if (! empty($uniqueConstraintAnnot->options)) { + $uniqueConstraint['options'] = $uniqueConstraintAnnot->options; + } + + if (! empty($uniqueConstraintAnnot->name)) { + $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; + } else { + $primaryTable['uniqueConstraints'][] = $uniqueConstraint; + } + } + } + + $metadata->setPrimaryTable($primaryTable); + + // Evaluate #[Cache] attribute + if (isset($classAttributes[Mapping\Cache::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Cache::class); + } + + $cacheAttribute = $classAttributes[Mapping\Cache::class]; + $cacheMap = [ + 'region' => $cacheAttribute->region, + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), + ]; + + $metadata->enableCache($cacheMap); + } + + // Evaluate InheritanceType attribute + if (isset($classAttributes[Mapping\InheritanceType::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\InheritanceType::class); + } + + $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class]; + + $metadata->setInheritanceType( + constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value), + ); + + if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { + // Evaluate DiscriminatorColumn attribute + if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) { + $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class]; + assert($discrColumnAttribute instanceof Mapping\DiscriminatorColumn); + + $columnDef = [ + 'name' => $discrColumnAttribute->name, + 'type' => $discrColumnAttribute->type ?? 'string', + 'length' => $discrColumnAttribute->length ?? 255, + 'columnDefinition' => $discrColumnAttribute->columnDefinition, + 'enumType' => $discrColumnAttribute->enumType, + ]; + + if ($discrColumnAttribute->options) { + $columnDef['options'] = $discrColumnAttribute->options; + } + + $metadata->setDiscriminatorColumn($columnDef); + } else { + $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); + } + + // Evaluate DiscriminatorMap attribute + if (isset($classAttributes[Mapping\DiscriminatorMap::class])) { + $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class]; + $metadata->setDiscriminatorMap($discrMapAttribute->value); + } + } + } + + // Evaluate DoctrineChangeTrackingPolicy attribute + if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ChangeTrackingPolicy::class); + } + + $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class]; + $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value)); + } + + foreach ($reflectionClass->getProperties() as $property) { + assert($property instanceof ReflectionProperty); + + if ($this->isRepeatedPropertyDeclaration($property, $metadata)) { + continue; + } + + $mapping = []; + $mapping['fieldName'] = $property->name; + + // Evaluate #[Cache] attribute + $cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class); + if ($cacheAttribute !== null) { + assert($cacheAttribute instanceof Mapping\Cache); + + $mapping['cache'] = $metadata->getAssociationCacheDefaults( + $mapping['fieldName'], + [ + 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), + 'region' => $cacheAttribute->region, + ], + ); + } + + // Check for JoinColumn/JoinColumns attributes + $joinColumns = []; + + $joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class); + + foreach ($joinColumnAttributes as $joinColumnAttribute) { + $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute); + } + + // Field can only be attributed with one of: + // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded + $columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class); + $oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class); + $oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class); + $manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class); + $manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class); + $embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class); + + if ($columnAttribute !== null) { + $mapping = $this->columnToArray($property->name, $columnAttribute); + + if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + $generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class); + + if ($generatedValueAttribute !== null) { + $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy)); + } + + if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) { + $metadata->setVersionMapping($mapping); + } + + $metadata->mapField($mapping); + + // Check for SequenceGenerator/TableGenerator definition + $seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class); + $customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class); + + if ($seqGeneratorAttribute !== null) { + $metadata->setSequenceGeneratorDefinition( + [ + 'sequenceName' => $seqGeneratorAttribute->sequenceName, + 'allocationSize' => $seqGeneratorAttribute->allocationSize, + 'initialValue' => $seqGeneratorAttribute->initialValue, + ], + ); + } elseif ($customGeneratorAttribute !== null) { + $metadata->setCustomGeneratorDefinition( + [ + 'class' => $customGeneratorAttribute->class, + ], + ); + } + } elseif ($oneToOneAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToOne::class); + } + + if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { + $mapping['id'] = true; + } + + $mapping['targetEntity'] = $oneToOneAttribute->targetEntity; + $mapping['joinColumns'] = $joinColumns; + $mapping['mappedBy'] = $oneToOneAttribute->mappedBy; + $mapping['inversedBy'] = $oneToOneAttribute->inversedBy; + $mapping['cascade'] = $oneToOneAttribute->cascade; + $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch); + $metadata->mapOneToOne($mapping); + } elseif ($oneToManyAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class); + } + + $mapping['mappedBy'] = $oneToManyAttribute->mappedBy; + $mapping['targetEntity'] = $oneToManyAttribute->targetEntity; + $mapping['cascade'] = $oneToManyAttribute->cascade; + $mapping['indexBy'] = $oneToManyAttribute->indexBy; + $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch); + + $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); + + if ($orderByAttribute !== null) { + $mapping['orderBy'] = $orderByAttribute->value; + } + + $metadata->mapOneToMany($mapping); + } elseif ($manyToOneAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class); + } + + $idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class); + + if ($idAttribute !== null) { + $mapping['id'] = true; + } + + $mapping['joinColumns'] = $joinColumns; + $mapping['cascade'] = $manyToOneAttribute->cascade; + $mapping['inversedBy'] = $manyToOneAttribute->inversedBy; + $mapping['targetEntity'] = $manyToOneAttribute->targetEntity; + $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch); + $metadata->mapManyToOne($mapping); + } elseif ($manyToManyAttribute !== null) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToMany::class); + } + + $joinTable = []; + $joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class); + + if ($joinTableAttribute !== null) { + $joinTable = [ + 'name' => $joinTableAttribute->name, + 'schema' => $joinTableAttribute->schema, + ]; + + if ($joinTableAttribute->options) { + $joinTable['options'] = $joinTableAttribute->options; + } + + foreach ($joinTableAttribute->joinColumns as $joinColumn) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); + } + } + + foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); + } + + $mapping['joinTable'] = $joinTable; + $mapping['targetEntity'] = $manyToManyAttribute->targetEntity; + $mapping['mappedBy'] = $manyToManyAttribute->mappedBy; + $mapping['inversedBy'] = $manyToManyAttribute->inversedBy; + $mapping['cascade'] = $manyToManyAttribute->cascade; + $mapping['indexBy'] = $manyToManyAttribute->indexBy; + $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval; + $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch); + + $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); + + if ($orderByAttribute !== null) { + $mapping['orderBy'] = $orderByAttribute->value; + } + + $metadata->mapManyToMany($mapping); + } elseif ($embeddedAttribute !== null) { + $mapping['class'] = $embeddedAttribute->class; + $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix; + + $metadata->mapEmbedded($mapping); + } + } + + // Evaluate AssociationOverrides attribute + if (isset($classAttributes[Mapping\AssociationOverrides::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AssociationOverride::class); + } + + $associationOverride = $classAttributes[Mapping\AssociationOverrides::class]; + + foreach ($associationOverride->overrides as $associationOverride) { + $override = []; + $fieldName = $associationOverride->name; + + // Check for JoinColumn/JoinColumns attributes + if ($associationOverride->joinColumns) { + $joinColumns = []; + + foreach ($associationOverride->joinColumns as $joinColumn) { + $joinColumns[] = $this->joinColumnToArray($joinColumn); + } + + $override['joinColumns'] = $joinColumns; + } + + if ($associationOverride->inverseJoinColumns) { + $joinColumns = []; + + foreach ($associationOverride->inverseJoinColumns as $joinColumn) { + $joinColumns[] = $this->joinColumnToArray($joinColumn); + } + + $override['inverseJoinColumns'] = $joinColumns; + } + + // Check for JoinTable attributes + if ($associationOverride->joinTable) { + $joinTableAnnot = $associationOverride->joinTable; + $joinTable = [ + 'name' => $joinTableAnnot->name, + 'schema' => $joinTableAnnot->schema, + 'joinColumns' => $override['joinColumns'] ?? [], + 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [], + ]; + + unset($override['joinColumns'], $override['inverseJoinColumns']); + + $override['joinTable'] = $joinTable; + } + + // Check for inversedBy + if ($associationOverride->inversedBy) { + $override['inversedBy'] = $associationOverride->inversedBy; + } + + // Check for `fetch` + if ($associationOverride->fetch) { + $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch); + } + + $metadata->setAssociationOverride($fieldName, $override); + } + } + + // Evaluate AttributeOverrides attribute + if (isset($classAttributes[Mapping\AttributeOverrides::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AttributeOverrides::class); + } + + $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class]; + + foreach ($attributeOverridesAnnot->overrides as $attributeOverride) { + $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column); + + $metadata->setAttributeOverride($attributeOverride->name, $mapping); + } + } + + // Evaluate EntityListeners attribute + if (isset($classAttributes[Mapping\EntityListeners::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\EntityListeners::class); + } + + $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class]; + + foreach ($entityListenersAttribute->value as $item) { + $listenerClassName = $metadata->fullyQualifiedClassName($item); + + if (! class_exists($listenerClassName)) { + throw MappingException::entityListenerClassNotFound($listenerClassName, $className); + } + + $hasMapping = false; + $listenerClass = new ReflectionClass($listenerClassName); + + foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + assert($method instanceof ReflectionMethod); + // find method callbacks. + $callbacks = $this->getMethodCallbacks($method); + $hasMapping = $hasMapping ?: ! empty($callbacks); + + foreach ($callbacks as $value) { + $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); + } + } + + // Evaluate the listener using naming convention. + if (! $hasMapping) { + EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); + } + } + } + + // Evaluate #[HasLifecycleCallbacks] attribute + if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) { + if ($metadata->isEmbeddedClass) { + throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\HasLifecycleCallbacks::class); + } + + foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + assert($method instanceof ReflectionMethod); + foreach ($this->getMethodCallbacks($method) as $value) { + $metadata->addLifecycleCallback($value[0], $value[1]); + } + } + } + } + + /** + * Attempts to resolve the fetch mode. + * + * @param class-string $className The class name. + * @param string $fetchMode The fetch mode. + * + * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata. + * + * @throws MappingException If the fetch mode is not valid. + */ + private function getFetchMode(string $className, string $fetchMode): int + { + if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { + throw MappingException::invalidFetchMode($className, $fetchMode); + } + + return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); + } + + /** + * Attempts to resolve the generated mode. + * + * @throws MappingException If the fetch mode is not valid. + */ + private function getGeneratedMode(string $generatedMode): int + { + if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) { + throw MappingException::invalidGeneratedMode($generatedMode); + } + + return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode); + } + + /** + * Parses the given method. + * + * @return list + * @psalm-return list + */ + private function getMethodCallbacks(ReflectionMethod $method): array + { + $callbacks = []; + $attributes = $this->reader->getMethodAttributes($method); + + foreach ($attributes as $attribute) { + if ($attribute instanceof Mapping\PrePersist) { + $callbacks[] = [$method->name, Events::prePersist]; + } + + if ($attribute instanceof Mapping\PostPersist) { + $callbacks[] = [$method->name, Events::postPersist]; + } + + if ($attribute instanceof Mapping\PreUpdate) { + $callbacks[] = [$method->name, Events::preUpdate]; + } + + if ($attribute instanceof Mapping\PostUpdate) { + $callbacks[] = [$method->name, Events::postUpdate]; + } + + if ($attribute instanceof Mapping\PreRemove) { + $callbacks[] = [$method->name, Events::preRemove]; + } + + if ($attribute instanceof Mapping\PostRemove) { + $callbacks[] = [$method->name, Events::postRemove]; + } + + if ($attribute instanceof Mapping\PostLoad) { + $callbacks[] = [$method->name, Events::postLoad]; + } + + if ($attribute instanceof Mapping\PreFlush) { + $callbacks[] = [$method->name, Events::preFlush]; + } + } + + return $callbacks; + } + + /** + * Parse the given JoinColumn as array + * + * @return mixed[] + * @psalm-return array{ + * name: string|null, + * unique: bool, + * nullable: bool, + * onDelete: mixed, + * columnDefinition: string|null, + * referencedColumnName: string, + * options?: array + * } + */ + private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn): array + { + $mapping = [ + 'name' => $joinColumn->name, + 'unique' => $joinColumn->unique, + 'nullable' => $joinColumn->nullable, + 'onDelete' => $joinColumn->onDelete, + 'columnDefinition' => $joinColumn->columnDefinition, + 'referencedColumnName' => $joinColumn->referencedColumnName, + ]; + + if ($joinColumn->options) { + $mapping['options'] = $joinColumn->options; + } + + return $mapping; + } + + /** + * Parse the given Column as array + * + * @return mixed[] + * @psalm-return array{ + * fieldName: string, + * type: mixed, + * scale: int, + * length: int, + * unique: bool, + * nullable: bool, + * precision: int, + * enumType?: class-string, + * options?: mixed[], + * columnName?: string, + * columnDefinition?: string + * } + */ + private function columnToArray(string $fieldName, Mapping\Column $column): array + { + $mapping = [ + 'fieldName' => $fieldName, + 'type' => $column->type, + 'scale' => $column->scale, + 'length' => $column->length, + 'unique' => $column->unique, + 'nullable' => $column->nullable, + 'precision' => $column->precision, + ]; + + if ($column->options) { + $mapping['options'] = $column->options; + } + + if (isset($column->name)) { + $mapping['columnName'] = $column->name; + } + + if (isset($column->columnDefinition)) { + $mapping['columnDefinition'] = $column->columnDefinition; + } + + if ($column->updatable === false) { + $mapping['notUpdatable'] = true; + } + + if ($column->insertable === false) { + $mapping['notInsertable'] = true; + } + + if ($column->generated !== null) { + $mapping['generated'] = $this->getGeneratedMode($column->generated); + } + + if ($column->enumType) { + $mapping['enumType'] = $column->enumType; + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php b/vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php new file mode 100644 index 0000000..2de622a --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php @@ -0,0 +1,146 @@ +, bool> */ + private array $isRepeatableAttribute = []; + + /** + * @psalm-return class-string-map> + * + * @template T of MappingAttribute + */ + public function getClassAttributes(ReflectionClass $class): array + { + return $this->convertToAttributeInstances($class->getAttributes()); + } + + /** + * @return class-string-map> + * + * @template T of MappingAttribute + */ + public function getMethodAttributes(ReflectionMethod $method): array + { + return $this->convertToAttributeInstances($method->getAttributes()); + } + + /** + * @return class-string-map> + * + * @template T of MappingAttribute + */ + public function getPropertyAttributes(ReflectionProperty $property): array + { + return $this->convertToAttributeInstances($property->getAttributes()); + } + + /** + * @param class-string $attributeName The name of the annotation. + * + * @return T|null + * + * @template T of MappingAttribute + */ + public function getPropertyAttribute(ReflectionProperty $property, string $attributeName) + { + if ($this->isRepeatable($attributeName)) { + throw new LogicException(sprintf( + 'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.', + $attributeName, + )); + } + + return $this->getPropertyAttributes($property)[$attributeName] ?? null; + } + + /** + * @param class-string $attributeName The name of the annotation. + * + * @return RepeatableAttributeCollection + * + * @template T of MappingAttribute + */ + public function getPropertyAttributeCollection( + ReflectionProperty $property, + string $attributeName, + ): RepeatableAttributeCollection { + if (! $this->isRepeatable($attributeName)) { + throw new LogicException(sprintf( + 'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.', + $attributeName, + )); + } + + return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection(); + } + + /** + * @param array $attributes + * + * @return class-string-map> + * + * @template T of MappingAttribute + */ + private function convertToAttributeInstances(array $attributes): array + { + $instances = []; + + foreach ($attributes as $attribute) { + $attributeName = $attribute->getName(); + assert(is_string($attributeName)); + // Make sure we only get Doctrine Attributes + if (! is_subclass_of($attributeName, MappingAttribute::class)) { + continue; + } + + $instance = $attribute->newInstance(); + assert($instance instanceof MappingAttribute); + + if ($this->isRepeatable($attributeName)) { + if (! isset($instances[$attributeName])) { + $instances[$attributeName] = new RepeatableAttributeCollection(); + } + + $collection = $instances[$attributeName]; + assert($collection instanceof RepeatableAttributeCollection); + $collection[] = $instance; + } else { + $instances[$attributeName] = $instance; + } + } + + return $instances; + } + + /** @param class-string $attributeClassName */ + private function isRepeatable(string $attributeClassName): bool + { + if (isset($this->isRepeatableAttribute[$attributeClassName])) { + return $this->isRepeatableAttribute[$attributeClassName]; + } + + $reflectionClass = new ReflectionClass($attributeClassName); + $attribute = $reflectionClass->getAttributes()[0]->newInstance(); + + return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php new file mode 100644 index 0000000..49e2e93 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php @@ -0,0 +1,528 @@ +|null */ + private array|null $tables = null; + + /** @var array */ + private array $classToTableNames = []; + + /** @psalm-var array */ + private array $manyToManyTables = []; + + /** @var mixed[] */ + private array $classNamesForTables = []; + + /** @var mixed[] */ + private array $fieldNamesForColumns = []; + + /** + * The namespace for the generated entities. + */ + private string|null $namespace = null; + + private Inflector $inflector; + + public function __construct(private readonly AbstractSchemaManager $sm) + { + $this->inflector = InflectorFactory::create()->build(); + } + + /** + * Set the namespace for the generated entities. + */ + public function setNamespace(string $namespace): void + { + $this->namespace = $namespace; + } + + public function isTransient(string $className): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames(): array + { + $this->reverseEngineerMappingFromDatabase(); + + return array_keys($this->classToTableNames); + } + + /** + * Sets class name for a table. + */ + public function setClassNameForTable(string $tableName, string $className): void + { + $this->classNamesForTables[$tableName] = $className; + } + + /** + * Sets field name for a column on a specific table. + */ + public function setFieldNameForColumn(string $tableName, string $columnName, string $fieldName): void + { + $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; + } + + /** + * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager. + * + * @param Table[] $entityTables + * @param Table[] $manyToManyTables + * @psalm-param list $entityTables + * @psalm-param list
$manyToManyTables + */ + public function setTables(array $entityTables, array $manyToManyTables): void + { + $this->tables = $this->manyToManyTables = $this->classToTableNames = []; + + foreach ($entityTables as $table) { + $className = $this->getClassNameForTable($table->getName()); + + $this->classToTableNames[$className] = $table->getName(); + $this->tables[$table->getName()] = $table; + } + + foreach ($manyToManyTables as $table) { + $this->manyToManyTables[$table->getName()] = $table; + } + } + + public function setInflector(Inflector $inflector): void + { + $this->inflector = $inflector; + } + + /** + * {@inheritDoc} + * + * @psalm-param class-string $className + * @psalm-param ClassMetadata $metadata + * + * @template T of object + */ + public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void + { + if (! $metadata instanceof ClassMetadata) { + throw new TypeError(sprintf( + 'Argument #2 passed to %s() must be an instance of %s, %s given.', + __METHOD__, + ClassMetadata::class, + get_debug_type($metadata), + )); + } + + $this->reverseEngineerMappingFromDatabase(); + + if (! isset($this->classToTableNames[$className])) { + throw new InvalidArgumentException('Unknown class ' . $className); + } + + $tableName = $this->classToTableNames[$className]; + + $metadata->name = $className; + $metadata->table['name'] = $tableName; + + $this->buildIndexes($metadata); + $this->buildFieldMappings($metadata); + $this->buildToOneAssociationMappings($metadata); + + foreach ($this->manyToManyTables as $manyTable) { + foreach ($manyTable->getForeignKeys() as $foreignKey) { + // foreign key maps to the table of the current entity, many to many association probably exists + if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) { + continue; + } + + $myFk = $foreignKey; + $otherFk = null; + + foreach ($manyTable->getForeignKeys() as $foreignKey) { + if ($foreignKey !== $myFk) { + $otherFk = $foreignKey; + break; + } + } + + if (! $otherFk) { + // the definition of this many to many table does not contain + // enough foreign key information to continue reverse engineering. + continue; + } + + $localColumn = current($myFk->getLocalColumns()); + + $associationMapping = []; + $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true); + $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); + + if (current($manyTable->getColumns())->getName() === $localColumn) { + $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); + $associationMapping['joinTable'] = [ + 'name' => strtolower($manyTable->getName()), + 'joinColumns' => [], + 'inverseJoinColumns' => [], + ]; + + $fkCols = $myFk->getForeignColumns(); + $cols = $myFk->getLocalColumns(); + + for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { + $associationMapping['joinTable']['joinColumns'][] = [ + 'name' => $cols[$i], + 'referencedColumnName' => $fkCols[$i], + ]; + } + + $fkCols = $otherFk->getForeignColumns(); + $cols = $otherFk->getLocalColumns(); + + for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { + $associationMapping['joinTable']['inverseJoinColumns'][] = [ + 'name' => $cols[$i], + 'referencedColumnName' => $fkCols[$i], + ]; + } + } else { + $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); + } + + $metadata->mapManyToMany($associationMapping); + + break; + } + } + } + + /** @throws MappingException */ + private function reverseEngineerMappingFromDatabase(): void + { + if ($this->tables !== null) { + return; + } + + $this->tables = $this->manyToManyTables = $this->classToTableNames = []; + + foreach ($this->sm->listTables() as $table) { + $tableName = $table->getName(); + $foreignKeys = $table->getForeignKeys(); + + $allForeignKeyColumns = []; + + foreach ($foreignKeys as $foreignKey) { + $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); + } + + $primaryKey = $table->getPrimaryKey(); + if ($primaryKey === null) { + throw new MappingException( + 'Table ' . $tableName . ' has no primary key. Doctrine does not ' . + "support reverse engineering from tables that don't have a primary key.", + ); + } + + $pkColumns = $primaryKey->getColumns(); + + sort($pkColumns); + sort($allForeignKeyColumns); + + if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) { + $this->manyToManyTables[$tableName] = $table; + } else { + // lower-casing is necessary because of Oracle Uppercase Tablenames, + // assumption is lower-case + underscore separated. + $className = $this->getClassNameForTable($tableName); + + $this->tables[$tableName] = $table; + $this->classToTableNames[$className] = $tableName; + } + } + } + + /** + * Build indexes from a class metadata. + */ + private function buildIndexes(ClassMetadata $metadata): void + { + $tableName = $metadata->table['name']; + $indexes = $this->tables[$tableName]->getIndexes(); + + foreach ($indexes as $index) { + if ($index->isPrimary()) { + continue; + } + + $indexName = $index->getName(); + $indexColumns = $index->getColumns(); + $constraintType = $index->isUnique() + ? 'uniqueConstraints' + : 'indexes'; + + $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns; + } + } + + /** + * Build field mapping from class metadata. + */ + private function buildFieldMappings(ClassMetadata $metadata): void + { + $tableName = $metadata->table['name']; + $columns = $this->tables[$tableName]->getColumns(); + $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); + $foreignKeys = $this->tables[$tableName]->getForeignKeys(); + $allForeignKeys = []; + + foreach ($foreignKeys as $foreignKey) { + $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns()); + } + + $ids = []; + $fieldMappings = []; + + foreach ($columns as $column) { + if (in_array($column->getName(), $allForeignKeys, true)) { + continue; + } + + $fieldMapping = $this->buildFieldMapping($tableName, $column); + + if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) { + $fieldMapping['id'] = true; + $ids[] = $fieldMapping; + } + + $fieldMappings[] = $fieldMapping; + } + + // We need to check for the columns here, because we might have associations as id as well. + if ($ids && count($primaryKeys) === 1) { + $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); + } + + foreach ($fieldMappings as $fieldMapping) { + $metadata->mapField($fieldMapping); + } + } + + /** + * Build field mapping from a schema column definition + * + * @return mixed[] + * @psalm-return array{ + * fieldName: string, + * columnName: string, + * type: string, + * nullable: bool, + * options: array{ + * unsigned?: bool, + * fixed?: bool, + * comment: string|null, + * default?: mixed + * }, + * precision?: int, + * scale?: int, + * length?: int|null + * } + */ + private function buildFieldMapping(string $tableName, Column $column): array + { + $fieldMapping = [ + 'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false), + 'columnName' => $column->getName(), + 'type' => Type::getTypeRegistry()->lookupName($column->getType()), + 'nullable' => ! $column->getNotnull(), + 'options' => [ + 'comment' => $column->getComment(), + ], + ]; + + // Type specific elements + switch ($fieldMapping['type']) { + case self::ARRAY: + case Types::BLOB: + case Types::GUID: + case self::OBJECT: + case Types::SIMPLE_ARRAY: + case Types::STRING: + case Types::TEXT: + $fieldMapping['length'] = $column->getLength(); + $fieldMapping['options']['fixed'] = $column->getFixed(); + break; + + case Types::DECIMAL: + case Types::FLOAT: + $fieldMapping['precision'] = $column->getPrecision(); + $fieldMapping['scale'] = $column->getScale(); + break; + + case Types::INTEGER: + case Types::BIGINT: + case Types::SMALLINT: + $fieldMapping['options']['unsigned'] = $column->getUnsigned(); + break; + } + + // Default + $default = $column->getDefault(); + if ($default !== null) { + $fieldMapping['options']['default'] = $default; + } + + return $fieldMapping; + } + + /** + * Build to one (one to one, many to one) association mapping from class metadata. + */ + private function buildToOneAssociationMappings(ClassMetadata $metadata): void + { + assert($this->tables !== null); + + $tableName = $metadata->table['name']; + $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); + $foreignKeys = $this->tables[$tableName]->getForeignKeys(); + + foreach ($foreignKeys as $foreignKey) { + $foreignTableName = $foreignKey->getForeignTableName(); + $fkColumns = $foreignKey->getLocalColumns(); + $fkForeignColumns = $foreignKey->getForeignColumns(); + $localColumn = current($fkColumns); + $associationMapping = [ + 'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true), + 'targetEntity' => $this->getClassNameForTable($foreignTableName), + ]; + + if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { + $associationMapping['fieldName'] .= '2'; // "foo" => "foo2" + } + + if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) { + $associationMapping['id'] = true; + } + + for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) { + $associationMapping['joinColumns'][] = [ + 'name' => $fkColumns[$i], + 'referencedColumnName' => $fkForeignColumns[$i], + ]; + } + + // Here we need to check if $fkColumns are the same as $primaryKeys + if (! array_diff($fkColumns, $primaryKeys)) { + $metadata->mapOneToOne($associationMapping); + } else { + $metadata->mapManyToOne($associationMapping); + } + } + } + + /** + * Retrieve schema table definition primary keys. + * + * @return string[] + */ + private function getTablePrimaryKeys(Table $table): array + { + try { + return $table->getPrimaryKey()->getColumns(); + } catch (SchemaException) { + // Do nothing + } + + return []; + } + + /** + * Returns the mapped class name for a table if it exists. Otherwise return "classified" version. + * + * @psalm-return class-string + */ + private function getClassNameForTable(string $tableName): string + { + if (isset($this->classNamesForTables[$tableName])) { + return $this->namespace . $this->classNamesForTables[$tableName]; + } + + return $this->namespace . $this->inflector->classify(strtolower($tableName)); + } + + /** + * Return the mapped field name for a column, if it exists. Otherwise return camelized version. + * + * @param bool $fk Whether the column is a foreignkey or not. + */ + private function getFieldNameForColumn( + string $tableName, + string $columnName, + bool $fk = false, + ): string { + if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) { + return $this->fieldNamesForColumns[$tableName][$columnName]; + } + + $columnName = strtolower($columnName); + + // Replace _id if it is a foreignkey column + if ($fk) { + $columnName = preg_replace('/_id$/', '', $columnName); + } + + return $this->inflector->camelize($columnName); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php new file mode 100644 index 0000000..7d85471 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php @@ -0,0 +1,44 @@ +class; + + if ( + isset($metadata->fieldMappings[$property->name]->declared) + && $metadata->fieldMappings[$property->name]->declared === $declaringClass + ) { + return true; + } + + if ( + isset($metadata->associationMappings[$property->name]->declared) + && $metadata->associationMappings[$property->name]->declared === $declaringClass + ) { + return true; + } + + return isset($metadata->embeddedClasses[$property->name]->declared) + && $metadata->embeddedClasses[$property->name]->declared === $declaringClass; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php b/vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php new file mode 100644 index 0000000..2f6ae93 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php @@ -0,0 +1,16 @@ + + * @template T of MappingAttribute + */ +final class RepeatableAttributeCollection extends ArrayObject +{ +} diff --git a/vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php new file mode 100644 index 0000000..486185f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php @@ -0,0 +1,25 @@ + + */ +class XmlDriver extends FileDriver +{ + public const DEFAULT_FILE_EXTENSION = '.dcm.xml'; + + /** + * {@inheritDoc} + */ + public function __construct( + string|array|FileLocator $locator, + string $fileExtension = self::DEFAULT_FILE_EXTENSION, + private readonly bool $isXsdValidationEnabled = true, + ) { + if (! extension_loaded('simplexml')) { + throw new LogicException( + 'The XML metadata driver cannot be enabled because the SimpleXML PHP extension is missing.' + . ' Please configure PHP with SimpleXML or choose a different metadata driver.', + ); + } + + if ($isXsdValidationEnabled && ! extension_loaded('dom')) { + throw new LogicException( + 'XSD validation cannot be enabled because the DOM extension is missing.', + ); + } + + parent::__construct($locator, $fileExtension); + } + + /** + * {@inheritDoc} + * + * @psalm-param class-string $className + * @psalm-param ClassMetadata $metadata + * + * @template T of object + */ + public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void + { + $xmlRoot = $this->getElement($className); + + if ($xmlRoot->getName() === 'entity') { + if (isset($xmlRoot['repository-class'])) { + $metadata->setCustomRepositoryClass((string) $xmlRoot['repository-class']); + } + + if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) { + $metadata->markReadOnly(); + } + } elseif ($xmlRoot->getName() === 'mapped-superclass') { + $metadata->setCustomRepositoryClass( + isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null, + ); + $metadata->isMappedSuperclass = true; + } elseif ($xmlRoot->getName() === 'embeddable') { + $metadata->isEmbeddedClass = true; + } else { + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); + } + + // Evaluate attributes + $primaryTable = []; + + if (isset($xmlRoot['table'])) { + $primaryTable['name'] = (string) $xmlRoot['table']; + } + + if (isset($xmlRoot['schema'])) { + $primaryTable['schema'] = (string) $xmlRoot['schema']; + } + + $metadata->setPrimaryTable($primaryTable); + + // Evaluate second level cache + if (isset($xmlRoot->cache)) { + $metadata->enableCache($this->cacheToArray($xmlRoot->cache)); + } + + if (isset($xmlRoot['inheritance-type'])) { + $inheritanceType = (string) $xmlRoot['inheritance-type']; + $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); + + if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { + // Evaluate + if (isset($xmlRoot->{'discriminator-column'})) { + $discrColumn = $xmlRoot->{'discriminator-column'}; + $columnDef = [ + 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, + 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', + 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, + 'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null, + 'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null, + ]; + + if (isset($discrColumn['options'])) { + assert($discrColumn['options'] instanceof SimpleXMLElement); + $columnDef['options'] = $this->parseOptions($discrColumn['options']->children()); + } + + $metadata->setDiscriminatorColumn($columnDef); + } else { + $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); + } + + // Evaluate + if (isset($xmlRoot->{'discriminator-map'})) { + $map = []; + assert($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} instanceof SimpleXMLElement); + foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) { + $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class']; + } + + $metadata->setDiscriminatorMap($map); + } + } + } + + // Evaluate + if (isset($xmlRoot['change-tracking-policy'])) { + $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' + . strtoupper((string) $xmlRoot['change-tracking-policy']))); + } + + // Evaluate + if (isset($xmlRoot->indexes)) { + $metadata->table['indexes'] = []; + foreach ($xmlRoot->indexes->index ?? [] as $indexXml) { + $index = []; + + if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) { + $index['columns'] = explode(',', (string) $indexXml['columns']); + } + + if (isset($indexXml['fields'])) { + $index['fields'] = explode(',', (string) $indexXml['fields']); + } + + if ( + isset($index['columns'], $index['fields']) + || ( + ! isset($index['columns']) + && ! isset($index['fields']) + ) + ) { + throw MappingException::invalidIndexConfiguration( + $className, + (string) ($indexXml['name'] ?? count($metadata->table['indexes'])), + ); + } + + if (isset($indexXml['flags'])) { + $index['flags'] = explode(',', (string) $indexXml['flags']); + } + + if (isset($indexXml->options)) { + $index['options'] = $this->parseOptions($indexXml->options->children()); + } + + if (isset($indexXml['name'])) { + $metadata->table['indexes'][(string) $indexXml['name']] = $index; + } else { + $metadata->table['indexes'][] = $index; + } + } + } + + // Evaluate + if (isset($xmlRoot->{'unique-constraints'})) { + $metadata->table['uniqueConstraints'] = []; + foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} ?? [] as $uniqueXml) { + $unique = []; + + if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) { + $unique['columns'] = explode(',', (string) $uniqueXml['columns']); + } + + if (isset($uniqueXml['fields'])) { + $unique['fields'] = explode(',', (string) $uniqueXml['fields']); + } + + if ( + isset($unique['columns'], $unique['fields']) + || ( + ! isset($unique['columns']) + && ! isset($unique['fields']) + ) + ) { + throw MappingException::invalidUniqueConstraintConfiguration( + $className, + (string) ($uniqueXml['name'] ?? count($metadata->table['uniqueConstraints'])), + ); + } + + if (isset($uniqueXml->options)) { + $unique['options'] = $this->parseOptions($uniqueXml->options->children()); + } + + if (isset($uniqueXml['name'])) { + $metadata->table['uniqueConstraints'][(string) $uniqueXml['name']] = $unique; + } else { + $metadata->table['uniqueConstraints'][] = $unique; + } + } + } + + if (isset($xmlRoot->options)) { + $metadata->table['options'] = $this->parseOptions($xmlRoot->options->children()); + } + + // The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions + // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception + // Evaluate mappings + if (isset($xmlRoot->field)) { + foreach ($xmlRoot->field as $fieldMapping) { + $mapping = $this->columnToArray($fieldMapping); + + if (isset($mapping['version'])) { + $metadata->setVersionMapping($mapping); + unset($mapping['version']); + } + + $metadata->mapField($mapping); + } + } + + if (isset($xmlRoot->embedded)) { + foreach ($xmlRoot->embedded as $embeddedMapping) { + $columnPrefix = isset($embeddedMapping['column-prefix']) + ? (string) $embeddedMapping['column-prefix'] + : null; + + $useColumnPrefix = isset($embeddedMapping['use-column-prefix']) + ? $this->evaluateBoolean($embeddedMapping['use-column-prefix']) + : true; + + $mapping = [ + 'fieldName' => (string) $embeddedMapping['name'], + 'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null, + 'columnPrefix' => $useColumnPrefix ? $columnPrefix : false, + ]; + + $metadata->mapEmbedded($mapping); + } + } + + // Evaluate mappings + $associationIds = []; + foreach ($xmlRoot->id ?? [] as $idElement) { + if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) { + $associationIds[(string) $idElement['name']] = true; + continue; + } + + $mapping = $this->columnToArray($idElement); + $mapping['id'] = true; + + $metadata->mapField($mapping); + + if (isset($idElement->generator)) { + $strategy = isset($idElement->generator['strategy']) ? + (string) $idElement->generator['strategy'] : 'AUTO'; + $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' + . $strategy)); + } + + // Check for SequenceGenerator/TableGenerator definition + if (isset($idElement->{'sequence-generator'})) { + $seqGenerator = $idElement->{'sequence-generator'}; + $metadata->setSequenceGeneratorDefinition( + [ + 'sequenceName' => (string) $seqGenerator['sequence-name'], + 'allocationSize' => (string) $seqGenerator['allocation-size'], + 'initialValue' => (string) $seqGenerator['initial-value'], + ], + ); + } elseif (isset($idElement->{'custom-id-generator'})) { + $customGenerator = $idElement->{'custom-id-generator'}; + $metadata->setCustomGeneratorDefinition( + [ + 'class' => (string) $customGenerator['class'], + ], + ); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'one-to-one'})) { + foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) { + $mapping = [ + 'fieldName' => (string) $oneToOneElement['field'], + ]; + + if (isset($oneToOneElement['target-entity'])) { + $mapping['targetEntity'] = (string) $oneToOneElement['target-entity']; + } + + if (isset($associationIds[$mapping['fieldName']])) { + $mapping['id'] = true; + } + + if (isset($oneToOneElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToOneElement['fetch']); + } + + if (isset($oneToOneElement['mapped-by'])) { + $mapping['mappedBy'] = (string) $oneToOneElement['mapped-by']; + } else { + if (isset($oneToOneElement['inversed-by'])) { + $mapping['inversedBy'] = (string) $oneToOneElement['inversed-by']; + } + + $joinColumns = []; + + if (isset($oneToOneElement->{'join-column'})) { + $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'}); + } elseif (isset($oneToOneElement->{'join-columns'})) { + foreach ($oneToOneElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinColumns[] = $this->joinColumnToArray($joinColumnElement); + } + } + + $mapping['joinColumns'] = $joinColumns; + } + + if (isset($oneToOneElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($oneToOneElement->cascade); + } + + if (isset($oneToOneElement['orphan-removal'])) { + $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']); + } + + // Evaluate second level cache + if (isset($oneToOneElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache)); + } + + $metadata->mapOneToOne($mapping); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'one-to-many'})) { + foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) { + $mapping = [ + 'fieldName' => (string) $oneToManyElement['field'], + 'mappedBy' => (string) $oneToManyElement['mapped-by'], + ]; + + if (isset($oneToManyElement['target-entity'])) { + $mapping['targetEntity'] = (string) $oneToManyElement['target-entity']; + } + + if (isset($oneToManyElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToManyElement['fetch']); + } + + if (isset($oneToManyElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($oneToManyElement->cascade); + } + + if (isset($oneToManyElement['orphan-removal'])) { + $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']); + } + + if (isset($oneToManyElement->{'order-by'})) { + $orderBy = []; + foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) { + /** @psalm-suppress DeprecatedConstant */ + $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) + ? (string) $orderByField['direction'] + : (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC); + } + + $mapping['orderBy'] = $orderBy; + } + + if (isset($oneToManyElement['index-by'])) { + $mapping['indexBy'] = (string) $oneToManyElement['index-by']; + } elseif (isset($oneToManyElement->{'index-by'})) { + throw new InvalidArgumentException(' is not a valid tag'); + } + + // Evaluate second level cache + if (isset($oneToManyElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache)); + } + + $metadata->mapOneToMany($mapping); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'many-to-one'})) { + foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) { + $mapping = [ + 'fieldName' => (string) $manyToOneElement['field'], + ]; + + if (isset($manyToOneElement['target-entity'])) { + $mapping['targetEntity'] = (string) $manyToOneElement['target-entity']; + } + + if (isset($associationIds[$mapping['fieldName']])) { + $mapping['id'] = true; + } + + if (isset($manyToOneElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToOneElement['fetch']); + } + + if (isset($manyToOneElement['inversed-by'])) { + $mapping['inversedBy'] = (string) $manyToOneElement['inversed-by']; + } + + $joinColumns = []; + + if (isset($manyToOneElement->{'join-column'})) { + $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'}); + } elseif (isset($manyToOneElement->{'join-columns'})) { + foreach ($manyToOneElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinColumns[] = $this->joinColumnToArray($joinColumnElement); + } + } + + $mapping['joinColumns'] = $joinColumns; + + if (isset($manyToOneElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($manyToOneElement->cascade); + } + + // Evaluate second level cache + if (isset($manyToOneElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache)); + } + + $metadata->mapManyToOne($mapping); + } + } + + // Evaluate mappings + if (isset($xmlRoot->{'many-to-many'})) { + foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) { + $mapping = [ + 'fieldName' => (string) $manyToManyElement['field'], + ]; + + if (isset($manyToManyElement['target-entity'])) { + $mapping['targetEntity'] = (string) $manyToManyElement['target-entity']; + } + + if (isset($manyToManyElement['fetch'])) { + $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToManyElement['fetch']); + } + + if (isset($manyToManyElement['orphan-removal'])) { + $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']); + } + + if (isset($manyToManyElement['mapped-by'])) { + $mapping['mappedBy'] = (string) $manyToManyElement['mapped-by']; + } elseif (isset($manyToManyElement->{'join-table'})) { + if (isset($manyToManyElement['inversed-by'])) { + $mapping['inversedBy'] = (string) $manyToManyElement['inversed-by']; + } + + $joinTableElement = $manyToManyElement->{'join-table'}; + $joinTable = [ + 'name' => (string) $joinTableElement['name'], + ]; + + if (isset($joinTableElement['schema'])) { + $joinTable['schema'] = (string) $joinTableElement['schema']; + } + + if (isset($joinTableElement->options)) { + $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); + } + + foreach ($joinTableElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + + foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + + $mapping['joinTable'] = $joinTable; + } + + if (isset($manyToManyElement->cascade)) { + $mapping['cascade'] = $this->getCascadeMappings($manyToManyElement->cascade); + } + + if (isset($manyToManyElement->{'order-by'})) { + $orderBy = []; + foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) { + /** @psalm-suppress DeprecatedConstant */ + $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) + ? (string) $orderByField['direction'] + : (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC); + } + + $mapping['orderBy'] = $orderBy; + } + + if (isset($manyToManyElement['index-by'])) { + $mapping['indexBy'] = (string) $manyToManyElement['index-by']; + } elseif (isset($manyToManyElement->{'index-by'})) { + throw new InvalidArgumentException(' is not a valid tag'); + } + + // Evaluate second level cache + if (isset($manyToManyElement->cache)) { + $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache)); + } + + $metadata->mapManyToMany($mapping); + } + } + + // Evaluate association-overrides + if (isset($xmlRoot->{'attribute-overrides'})) { + foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} ?? [] as $overrideElement) { + $fieldName = (string) $overrideElement['name']; + foreach ($overrideElement->field ?? [] as $field) { + $mapping = $this->columnToArray($field); + $mapping['fieldName'] = $fieldName; + $metadata->setAttributeOverride($fieldName, $mapping); + } + } + } + + // Evaluate association-overrides + if (isset($xmlRoot->{'association-overrides'})) { + foreach ($xmlRoot->{'association-overrides'}->{'association-override'} ?? [] as $overrideElement) { + $fieldName = (string) $overrideElement['name']; + $override = []; + + // Check for join-columns + if (isset($overrideElement->{'join-columns'})) { + $joinColumns = []; + foreach ($overrideElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinColumns[] = $this->joinColumnToArray($joinColumnElement); + } + + $override['joinColumns'] = $joinColumns; + } + + // Check for join-table + if ($overrideElement->{'join-table'}) { + $joinTable = null; + $joinTableElement = $overrideElement->{'join-table'}; + + $joinTable = [ + 'name' => (string) $joinTableElement['name'], + 'schema' => (string) $joinTableElement['schema'], + ]; + + if (isset($joinTableElement->options)) { + $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); + } + + if (isset($joinTableElement->{'join-columns'})) { + foreach ($joinTableElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + } + + if (isset($joinTableElement->{'inverse-join-columns'})) { + foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { + $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); + } + } + + $override['joinTable'] = $joinTable; + } + + // Check for inversed-by + if (isset($overrideElement->{'inversed-by'})) { + $override['inversedBy'] = (string) $overrideElement->{'inversed-by'}['name']; + } + + // Check for `fetch` + if (isset($overrideElement['fetch'])) { + $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . (string) $overrideElement['fetch']); + } + + $metadata->setAssociationOverride($fieldName, $override); + } + } + + // Evaluate + if (isset($xmlRoot->{'lifecycle-callbacks'})) { + foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} ?? [] as $lifecycleCallback) { + $metadata->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string) $lifecycleCallback['type'])); + } + } + + // Evaluate entity listener + if (isset($xmlRoot->{'entity-listeners'})) { + foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} ?? [] as $listenerElement) { + $className = (string) $listenerElement['class']; + // Evaluate the listener using naming convention. + if ($listenerElement->count() === 0) { + EntityListenerBuilder::bindEntityListener($metadata, $className); + + continue; + } + + foreach ($listenerElement as $callbackElement) { + $eventName = (string) $callbackElement['type']; + $methodName = (string) $callbackElement['method']; + + $metadata->addEntityListener($eventName, $className, $methodName); + } + } + } + } + + /** + * Parses (nested) option elements. + * + * @return mixed[] The options array. + * @psalm-return array|bool|string> + */ + private function parseOptions(SimpleXMLElement|null $options): array + { + $array = []; + + foreach ($options ?? [] as $option) { + if ($option->count()) { + $value = $this->parseOptions($option->children()); + } else { + $value = (string) $option; + } + + $attributes = $option->attributes(); + + if (isset($attributes->name)) { + $nameAttribute = (string) $attributes->name; + $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true) + ? $this->evaluateBoolean($value) + : $value; + } else { + $array[] = $value; + } + } + + return $array; + } + + /** + * Constructs a joinColumn mapping array based on the information + * found in the given SimpleXMLElement. + * + * @param SimpleXMLElement $joinColumnElement The XML element. + * + * @return mixed[] The mapping array. + * @psalm-return array{ + * name: string, + * referencedColumnName: string, + * unique?: bool, + * nullable?: bool, + * onDelete?: string, + * columnDefinition?: string, + * options?: mixed[] + * } + */ + private function joinColumnToArray(SimpleXMLElement $joinColumnElement): array + { + $joinColumn = [ + 'name' => (string) $joinColumnElement['name'], + 'referencedColumnName' => (string) $joinColumnElement['referenced-column-name'], + ]; + + if (isset($joinColumnElement['unique'])) { + $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']); + } + + if (isset($joinColumnElement['nullable'])) { + $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']); + } + + if (isset($joinColumnElement['on-delete'])) { + $joinColumn['onDelete'] = (string) $joinColumnElement['on-delete']; + } + + if (isset($joinColumnElement['column-definition'])) { + $joinColumn['columnDefinition'] = (string) $joinColumnElement['column-definition']; + } + + if (isset($joinColumnElement['options'])) { + $joinColumn['options'] = $this->parseOptions($joinColumnElement['options'] ? $joinColumnElement['options']->children() : null); + } + + return $joinColumn; + } + + /** + * Parses the given field as array. + * + * @return mixed[] + * @psalm-return array{ + * fieldName: string, + * type?: string, + * columnName?: string, + * length?: int, + * precision?: int, + * scale?: int, + * unique?: bool, + * nullable?: bool, + * notInsertable?: bool, + * notUpdatable?: bool, + * enumType?: string, + * version?: bool, + * columnDefinition?: string, + * options?: array + * } + */ + private function columnToArray(SimpleXMLElement $fieldMapping): array + { + $mapping = [ + 'fieldName' => (string) $fieldMapping['name'], + ]; + + if (isset($fieldMapping['type'])) { + $mapping['type'] = (string) $fieldMapping['type']; + } + + if (isset($fieldMapping['column'])) { + $mapping['columnName'] = (string) $fieldMapping['column']; + } + + if (isset($fieldMapping['length'])) { + $mapping['length'] = (int) $fieldMapping['length']; + } + + if (isset($fieldMapping['precision'])) { + $mapping['precision'] = (int) $fieldMapping['precision']; + } + + if (isset($fieldMapping['scale'])) { + $mapping['scale'] = (int) $fieldMapping['scale']; + } + + if (isset($fieldMapping['unique'])) { + $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']); + } + + if (isset($fieldMapping['nullable'])) { + $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); + } + + if (isset($fieldMapping['insertable']) && ! $this->evaluateBoolean($fieldMapping['insertable'])) { + $mapping['notInsertable'] = true; + } + + if (isset($fieldMapping['updatable']) && ! $this->evaluateBoolean($fieldMapping['updatable'])) { + $mapping['notUpdatable'] = true; + } + + if (isset($fieldMapping['generated'])) { + $mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . (string) $fieldMapping['generated']); + } + + if (isset($fieldMapping['version']) && $fieldMapping['version']) { + $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); + } + + if (isset($fieldMapping['column-definition'])) { + $mapping['columnDefinition'] = (string) $fieldMapping['column-definition']; + } + + if (isset($fieldMapping['enum-type'])) { + $mapping['enumType'] = (string) $fieldMapping['enum-type']; + } + + if (isset($fieldMapping->options)) { + $mapping['options'] = $this->parseOptions($fieldMapping->options->children()); + } + + return $mapping; + } + + /** + * Parse / Normalize the cache configuration + * + * @return mixed[] + * @psalm-return array{usage: int|null, region?: string} + */ + private function cacheToArray(SimpleXMLElement $cacheMapping): array + { + $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null; + $usage = isset($cacheMapping['usage']) ? strtoupper((string) $cacheMapping['usage']) : null; + + if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) { + throw new InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage)); + } + + if ($usage) { + $usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage); + } + + return [ + 'usage' => $usage, + 'region' => $region, + ]; + } + + /** + * Gathers a list of cascade options found in the given cascade element. + * + * @param SimpleXMLElement $cascadeElement The cascade element. + * + * @return string[] The list of cascade options. + * @psalm-return list + */ + private function getCascadeMappings(SimpleXMLElement $cascadeElement): array + { + $cascades = []; + $children = $cascadeElement->children(); + assert($children !== null); + + foreach ($children as $action) { + // According to the JPA specifications, XML uses "cascade-persist" + // instead of "persist". Here, both variations + // are supported because Attribute uses "persist" + // and we want to make sure that this driver doesn't need to know + // anything about the supported cascading actions + $cascades[] = str_replace('cascade-', '', $action->getName()); + } + + return $cascades; + } + + /** + * {@inheritDoc} + */ + protected function loadMappingFile($file) + { + $this->validateMapping($file); + $result = []; + // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577 + $xmlElement = simplexml_load_string(file_get_contents($file)); + assert($xmlElement !== false); + + if (isset($xmlElement->entity)) { + foreach ($xmlElement->entity as $entityElement) { + /** @psalm-var class-string $entityName */ + $entityName = (string) $entityElement['name']; + $result[$entityName] = $entityElement; + } + } elseif (isset($xmlElement->{'mapped-superclass'})) { + foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) { + /** @psalm-var class-string $className */ + $className = (string) $mappedSuperClass['name']; + $result[$className] = $mappedSuperClass; + } + } elseif (isset($xmlElement->embeddable)) { + foreach ($xmlElement->embeddable as $embeddableElement) { + /** @psalm-var class-string $embeddableName */ + $embeddableName = (string) $embeddableElement['name']; + $result[$embeddableName] = $embeddableElement; + } + } + + return $result; + } + + private function validateMapping(string $file): void + { + if (! $this->isXsdValidationEnabled) { + return; + } + + $backedUpErrorSetting = libxml_use_internal_errors(true); + + try { + $document = new DOMDocument(); + $document->load($file); + + if (! $document->schemaValidate(__DIR__ . '/../../../doctrine-mapping.xsd')) { + throw MappingException::fromLibXmlErrors(libxml_get_errors()); + } + } finally { + libxml_clear_errors(); + libxml_use_internal_errors($backedUpErrorSetting); + } + } + + protected function evaluateBoolean(mixed $element): bool + { + $flag = (string) $element; + + return $flag === 'true' || $flag === '1'; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Embeddable.php b/vendor/doctrine/orm/src/Mapping/Embeddable.php new file mode 100644 index 0000000..b8dfea0 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Embeddable.php @@ -0,0 +1,12 @@ + */ +final class EmbeddedClassMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + public string|false|null $columnPrefix = null; + public string|null $declaredField = null; + public string|null $originalField = null; + + /** + * This is set when this embedded-class field is inherited by this class + * from another (inheritance) parent entity class. The value is + * the FQCN of the topmost entity class that contains mapping information + * for this field. (If there are transient classes in the class hierarchy, + * these are ignored, so the class property may in fact come from a class + * further up in the PHP class hierarchy.) Fields initially declared in + * mapped superclasses are not considered 'inherited' in the + * nearest entity subclasses. + * + * @var class-string|null + */ + public string|null $inherited = null; + + /** + * This is set when the embedded-class field does not appear for the first + * time in this class, but is originally declared in another parent + * entity or mapped superclass. The value is the FQCN of the + * topmost non-transient class that contains mapping information for this + * field. + * + * @var class-string|null + */ + public string|null $declared = null; + + /** @param class-string $class */ + public function __construct(public string $class) + { + } + + /** + * @psalm-param array{ + * class: class-string, + * columnPrefix?: false|string|null, + * declaredField?: string|null, + * originalField?: string|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self($mappingArray['class']); + foreach ($mappingArray as $key => $value) { + if ($key === 'class') { + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value; + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['class']; + + if ($this->columnPrefix) { + $serialized[] = 'columnPrefix'; + } + + foreach (['declaredField', 'originalField', 'inherited', 'declared'] as $property) { + if ($this->$property !== null) { + $serialized[] = $property; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Entity.php b/vendor/doctrine/orm/src/Mapping/Entity.php new file mode 100644 index 0000000..0e27913 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Entity.php @@ -0,0 +1,20 @@ +>|null $repositoryClass */ + public function __construct( + public readonly string|null $repositoryClass = null, + public readonly bool $readOnly = false, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php b/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php new file mode 100644 index 0000000..eabc217 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/EntityListenerResolver.php @@ -0,0 +1,30 @@ + $value */ + public function __construct( + public readonly array $value = [], + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php b/vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php new file mode 100644 index 0000000..b9e10bf --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Exception/InvalidCustomGenerator.php @@ -0,0 +1,28 @@ + */ +final class FieldMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + /** The database length of the column. Optional. Default value taken from the type. */ + public int|null $length = null; + /** + * Marks the field as the primary key of the entity. Multiple + * fields of an entity can have the id attribute, forming a composite key. + */ + public bool|null $id = null; + public bool|null $nullable = null; + public bool|null $notInsertable = null; + public bool|null $notUpdatable = null; + public string|null $columnDefinition = null; + /** @psalm-var ClassMetadata::GENERATED_*|null */ + public int|null $generated = null; + /** @var class-string|null */ + public string|null $enumType = null; + /** + * The precision of a decimal column. + * Only valid if the column type is decimal + */ + public int|null $precision = null; + /** + * The scale of a decimal column. + * Only valid if the column type is decimal + */ + public int|null $scale = null; + /** Whether a unique constraint should be generated for the column. */ + public bool|null $unique = null; + /** + * @var class-string|null This is set when the field is inherited by this + * class from another (inheritance) parent entity class. The value + * is the FQCN of the topmost entity class that contains mapping information + * for this field. (If there are transient classes in the class hierarchy, + * these are ignored, so the class property may in fact come from a class + * further up in the PHP class hierarchy.) + * Fields initially declared in mapped superclasses are + * not considered 'inherited' in the nearest entity subclasses. + */ + public string|null $inherited = null; + + public string|null $originalClass = null; + public string|null $originalField = null; + public bool|null $quoted = null; + /** + * @var class-string|null This is set when the field does not appear for + * the first time in this class, but is originally declared in another + * parent entity or mapped superclass. The value is the FQCN of + * the topmost non-transient class that contains mapping information for + * this field. + */ + public string|null $declared = null; + public string|null $declaredField = null; + public array|null $options = null; + public bool|null $version = null; + public string|int|null $default = null; + + /** + * @param string $type The type name of the mapped field. Can be one of + * Doctrine's mapping types or a custom mapping type. + * @param string $fieldName The name of the field in the Entity. + * @param string $columnName The column name. Optional. Defaults to the field name. + */ + public function __construct( + public string $type, + public string $fieldName, + public string $columnName, + ) { + } + + /** + * @param array $mappingArray + * @psalm-param array{ + * type: string, + * fieldName: string, + * columnName: string, + * length?: int|null, + * id?: bool|null, + * nullable?: bool|null, + * notInsertable?: bool|null, + * notUpdatable?: bool|null, + * columnDefinition?: string|null, + * generated?: ClassMetadata::GENERATED_*|null, + * enumType?: string|null, + * precision?: int|null, + * scale?: int|null, + * unique?: bool|null, + * inherited?: string|null, + * originalClass?: string|null, + * originalField?: string|null, + * quoted?: bool|null, + * declared?: string|null, + * declaredField?: string|null, + * options?: array|null, + * version?: bool|null, + * default?: string|int|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self( + $mappingArray['type'], + $mappingArray['fieldName'], + $mappingArray['columnName'], + ); + foreach ($mappingArray as $key => $value) { + if (in_array($key, ['type', 'fieldName', 'columnName'])) { + continue; + } + + if (property_exists($mapping, $key)) { + $mapping->$key = $value; + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = ['type', 'fieldName', 'columnName']; + + foreach (['nullable', 'notInsertable', 'notUpdatable', 'id', 'unique', 'version', 'quoted'] as $boolKey) { + if ($this->$boolKey) { + $serialized[] = $boolKey; + } + } + + foreach ( + [ + 'length', + 'columnDefinition', + 'generated', + 'enumType', + 'precision', + 'scale', + 'inherited', + 'originalClass', + 'originalField', + 'declared', + 'declaredField', + 'options', + 'default', + ] as $key + ) { + if ($this->$key !== null) { + $serialized[] = $key; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/GeneratedValue.php b/vendor/doctrine/orm/src/Mapping/GeneratedValue.php new file mode 100644 index 0000000..aca5f4b --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/GeneratedValue.php @@ -0,0 +1,17 @@ +|null $columns + * @param array|null $fields + * @param array|null $flags + * @param array|null $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly array|null $columns = null, + public readonly array|null $fields = null, + public readonly array|null $flags = null, + public readonly array|null $options = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/InheritanceType.php b/vendor/doctrine/orm/src/Mapping/InheritanceType.php new file mode 100644 index 0000000..c042ee7 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/InheritanceType.php @@ -0,0 +1,17 @@ +mappedBy; + } + + /** @return list */ + public function __sleep(): array + { + return [ + ...parent::__sleep(), + 'mappedBy', + ]; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumn.php b/vendor/doctrine/orm/src/Mapping/JoinColumn.php new file mode 100644 index 0000000..9a049fb --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumn.php @@ -0,0 +1,13 @@ + */ +final class JoinColumnMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + public bool|null $unique = null; + public bool|null $quoted = null; + public string|null $fieldName = null; + public string|null $onDelete = null; + public string|null $columnDefinition = null; + public bool|null $nullable = null; + + /** @var array|null */ + public array|null $options = null; + + public function __construct( + public string $name, + public string $referencedColumnName, + ) { + } + + /** + * @param array $mappingArray + * @psalm-param array{ + * name: string, + * referencedColumnName: string, + * unique?: bool|null, + * quoted?: bool|null, + * fieldName?: string|null, + * onDelete?: string|null, + * columnDefinition?: string|null, + * nullable?: bool|null, + * options?: array|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self($mappingArray['name'], $mappingArray['referencedColumnName']); + foreach ($mappingArray as $key => $value) { + if (property_exists($mapping, $key) && $value !== null) { + $mapping->$key = $value; + } + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = []; + + foreach (['name', 'fieldName', 'onDelete', 'columnDefinition', 'referencedColumnName', 'options'] as $stringOrArrayKey) { + if ($this->$stringOrArrayKey !== null) { + $serialized[] = $stringOrArrayKey; + } + } + + foreach (['unique', 'quoted', 'nullable'] as $boolKey) { + if ($this->$boolKey !== null) { + $serialized[] = $boolKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php b/vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php new file mode 100644 index 0000000..7d13295 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumnProperties.php @@ -0,0 +1,21 @@ + $options */ + public function __construct( + public readonly string|null $name = null, + public readonly string $referencedColumnName = 'id', + public readonly bool $unique = false, + public readonly bool $nullable = true, + public readonly mixed $onDelete = null, + public readonly string|null $columnDefinition = null, + public readonly string|null $fieldName = null, + public readonly array $options = [], + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumns.php b/vendor/doctrine/orm/src/Mapping/JoinColumns.php new file mode 100644 index 0000000..f166b76 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumns.php @@ -0,0 +1,14 @@ + $value */ + public function __construct( + public readonly array $value, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinTable.php b/vendor/doctrine/orm/src/Mapping/JoinTable.php new file mode 100644 index 0000000..0558761 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinTable.php @@ -0,0 +1,35 @@ + */ + public readonly array $joinColumns; + + /** @var array */ + public readonly array $inverseJoinColumns; + + /** + * @param array|JoinColumn $joinColumns + * @param array|JoinColumn $inverseJoinColumns + * @param array $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $schema = null, + array|JoinColumn $joinColumns = [], + array|JoinColumn $inverseJoinColumns = [], + public readonly array $options = [], + ) { + $this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns; + $this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn + ? [$inverseJoinColumns] + : $inverseJoinColumns; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/JoinTableMapping.php b/vendor/doctrine/orm/src/Mapping/JoinTableMapping.php new file mode 100644 index 0000000..c8b4968 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinTableMapping.php @@ -0,0 +1,115 @@ + */ +final class JoinTableMapping implements ArrayAccess +{ + use ArrayAccessImplementation; + + public bool|null $quoted = null; + + /** @var list */ + public array $joinColumns = []; + + /** @var list */ + public array $inverseJoinColumns = []; + + /** @var array */ + public array $options = []; + + public string|null $schema = null; + + public function __construct(public string $name) + { + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * name: string, + * quoted?: bool|null, + * joinColumns?: mixed[], + * inverseJoinColumns?: mixed[], + * schema?: string|null, + * options?: array + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): self + { + $mapping = new self($mappingArray['name']); + + foreach (['quoted', 'schema', 'options'] as $key) { + if (isset($mappingArray[$key])) { + $mapping->$key = $mappingArray[$key]; + } + } + + if (isset($mappingArray['joinColumns'])) { + foreach ($mappingArray['joinColumns'] as $column) { + $mapping->joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + } + + if (isset($mappingArray['inverseJoinColumns'])) { + foreach ($mappingArray['inverseJoinColumns'] as $column) { + $mapping->inverseJoinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + } + + return $mapping; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if (in_array($offset, ['joinColumns', 'inverseJoinColumns'], true)) { + $joinColumns = []; + foreach ($value as $column) { + $joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + + $value = $joinColumns; + } + + $this->$offset = $value; + } + + /** @return mixed[] */ + public function toArray(): array + { + $array = (array) $this; + + $toArray = static fn (JoinColumnMapping $column): array => (array) $column; + $array['joinColumns'] = array_map($toArray, $array['joinColumns']); + $array['inverseJoinColumns'] = array_map($toArray, $array['inverseJoinColumns']); + + return $array; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = []; + + foreach (['joinColumns', 'inverseJoinColumns', 'name', 'schema', 'options'] as $stringOrArrayKey) { + if ($this->$stringOrArrayKey !== null) { + $serialized[] = $stringOrArrayKey; + } + } + + foreach (['quoted'] as $boolKey) { + if ($this->$boolKey) { + $serialized[] = $boolKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ManyToMany.php b/vendor/doctrine/orm/src/Mapping/ManyToMany.php new file mode 100644 index 0000000..d90a762 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToMany.php @@ -0,0 +1,27 @@ + */ + public array $joinTableColumns = []; + + /** @var array */ + public array $relationToSourceKeyColumns = []; + /** @var array */ + public array $relationToTargetKeyColumns = []; + + /** @return array */ + public function toArray(): array + { + $array = parent::toArray(); + + $array['joinTable'] = $this->joinTable->toArray(); + + return $array; + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArrayAndNamingStrategy(array $mappingArray, NamingStrategy $namingStrategy): self + { + if (isset($mappingArray['joinTable']['joinColumns'])) { + foreach ($mappingArray['joinTable']['joinColumns'] as $key => $joinColumn) { + if (empty($joinColumn['name'])) { + $mappingArray['joinTable']['joinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( + $mappingArray['sourceEntity'], + $joinColumn['referencedColumnName'] ?? null, + ); + } + } + } + + if (isset($mappingArray['joinTable']['inverseJoinColumns'])) { + foreach ($mappingArray['joinTable']['inverseJoinColumns'] as $key => $joinColumn) { + if (empty($joinColumn['name'])) { + $mappingArray['joinTable']['inverseJoinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( + $mappingArray['targetEntity'], + $joinColumn['referencedColumnName'] ?? null, + ); + } + } + } + + // owning side MUST have a join table + if (! isset($mappingArray['joinTable']) || ! isset($mappingArray['joinTable']['name'])) { + $mappingArray['joinTable']['name'] = $namingStrategy->joinTableName( + $mappingArray['sourceEntity'], + $mappingArray['targetEntity'], + $mappingArray['fieldName'], + ); + } + + $mapping = parent::fromMappingArray($mappingArray); + + $selfReferencingEntityWithoutJoinColumns = $mapping->sourceEntity === $mapping->targetEntity + && $mapping->joinTable->joinColumns === [] + && $mapping->joinTable->inverseJoinColumns === []; + + if ($mapping->joinTable->joinColumns === []) { + $mapping->joinTable->joinColumns = [ + JoinColumnMapping::fromMappingArray([ + 'name' => $namingStrategy->joinKeyColumnName($mapping->sourceEntity, $selfReferencingEntityWithoutJoinColumns ? 'source' : null), + 'referencedColumnName' => $namingStrategy->referenceColumnName(), + 'onDelete' => 'CASCADE', + ]), + ]; + } + + if ($mapping->joinTable->inverseJoinColumns === []) { + $mapping->joinTable->inverseJoinColumns = [ + JoinColumnMapping::fromMappingArray([ + 'name' => $namingStrategy->joinKeyColumnName($mapping->targetEntity, $selfReferencingEntityWithoutJoinColumns ? 'target' : null), + 'referencedColumnName' => $namingStrategy->referenceColumnName(), + 'onDelete' => 'CASCADE', + ]), + ]; + } + + $mapping->joinTableColumns = []; + + foreach ($mapping->joinTable->joinColumns as $joinColumn) { + if (empty($joinColumn->referencedColumnName)) { + $joinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); + } + + if ($joinColumn->name[0] === '`') { + $joinColumn->name = trim($joinColumn->name, '`'); + $joinColumn->quoted = true; + } + + if ($joinColumn->referencedColumnName[0] === '`') { + $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`'); + $joinColumn->quoted = true; + } + + if (isset($joinColumn->onDelete) && strtolower($joinColumn->onDelete) === 'cascade') { + $mapping->isOnDeleteCascade = true; + } + + $mapping->relationToSourceKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName; + $mapping->joinTableColumns[] = $joinColumn->name; + } + + foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) { + if (empty($inverseJoinColumn->referencedColumnName)) { + $inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); + } + + if ($inverseJoinColumn->name[0] === '`') { + $inverseJoinColumn->name = trim($inverseJoinColumn->name, '`'); + $inverseJoinColumn->quoted = true; + } + + if ($inverseJoinColumn->referencedColumnName[0] === '`') { + $inverseJoinColumn->referencedColumnName = trim($inverseJoinColumn->referencedColumnName, '`'); + $inverseJoinColumn->quoted = true; + } + + if (isset($inverseJoinColumn->onDelete) && strtolower($inverseJoinColumn->onDelete) === 'cascade') { + $mapping->isOnDeleteCascade = true; + } + + $mapping->relationToTargetKeyColumns[$inverseJoinColumn->name] = $inverseJoinColumn->referencedColumnName; + $mapping->joinTableColumns[] = $inverseJoinColumn->name; + } + + return $mapping; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = parent::__sleep(); + $serialized[] = 'joinTable'; + $serialized[] = 'joinTableColumns'; + + foreach (['relationToSourceKeyColumns', 'relationToTargetKeyColumns'] as $arrayKey) { + if ($this->$arrayKey !== null) { + $serialized[] = $arrayKey; + } + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ManyToOne.php b/vendor/doctrine/orm/src/Mapping/ManyToOne.php new file mode 100644 index 0000000..8fccff3 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToOne.php @@ -0,0 +1,24 @@ +|null $repositoryClass */ + public function __construct( + public readonly string|null $repositoryClass = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/MappingAttribute.php b/vendor/doctrine/orm/src/Mapping/MappingAttribute.php new file mode 100644 index 0000000..61091a9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/MappingAttribute.php @@ -0,0 +1,10 @@ + $map + */ + public static function duplicateDiscriminatorEntry(string $className, array $entries, array $map): self + { + return new self( + 'The entries ' . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . + 'If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. ' . + 'The entries of the current map are: @DiscriminatorMap({' . implode(', ', array_map( + static fn ($a, $b) => sprintf("'%s': '%s'", $a, $b), + array_keys($map), + array_values($map), + )) . '})', + ); + } + + /** + * @param class-string $rootEntityClass + * @param class-string $childEntityClass + */ + public static function missingInheritanceTypeDeclaration(string $rootEntityClass, string $childEntityClass): self + { + return new self(sprintf( + "Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared.", + $childEntityClass, + $rootEntityClass, + )); + } + + public static function missingDiscriminatorMap(string $className): self + { + return new self(sprintf( + "Entity class '%s' is using inheritance but no discriminator map was defined.", + $className, + )); + } + + public static function missingDiscriminatorColumn(string $className): self + { + return new self(sprintf( + "Entity class '%s' is using inheritance but no discriminator column was defined.", + $className, + )); + } + + public static function invalidDiscriminatorColumnType(string $className, string $type): self + { + return new self(sprintf( + "Discriminator column type on entity class '%s' is not allowed to be '%s'. 'string' or 'integer' type variables are suggested!", + $className, + $type, + )); + } + + public static function nameIsMandatoryForDiscriminatorColumns(string $className): self + { + return new self(sprintf("Discriminator column name on entity class '%s' is not defined.", $className)); + } + + public static function cannotVersionIdField(string $className, string $fieldName): self + { + return new self(sprintf( + "Setting Id field '%s' as versionable in entity class '%s' is not supported.", + $fieldName, + $className, + )); + } + + public static function duplicateColumnName(string $className, string $columnName): self + { + return new self("Duplicate definition of column '" . $columnName . "' on entity '" . $className . "' in a field or discriminator column mapping."); + } + + public static function illegalToManyAssociationOnMappedSuperclass(string $className, string $field): self + { + return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '" . $className . '#' . $field . "'."); + } + + public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId(string $className, string $targetEntity, string $targetField): self + { + return new self("It is not possible to map entity '" . $className . "' with a composite primary key " . + "as part of the primary key of another entity '" . $targetEntity . '#' . $targetField . "'."); + } + + public static function noSingleAssociationJoinColumnFound(string $className, string $field): self + { + return new self(sprintf("'%s#%s' is not an association with a single join column.", $className, $field)); + } + + public static function noFieldNameFoundForColumn(string $className, string $column): self + { + return new self(sprintf( + "Cannot find a field on '%s' that is mapped to column '%s'. Either the " . + 'field does not exist or an association exists but it has multiple join columns.', + $className, + $column, + )); + } + + public static function illegalOrphanRemovalOnIdentifierAssociation(string $className, string $field): self + { + return new self(sprintf( + "The orphan removal option is not allowed on an association that is part of the identifier in '%s#%s'.", + $className, + $field, + )); + } + + public static function illegalOrphanRemoval(string $className, string $field): self + { + return new self('Orphan removal is only allowed on one-to-one and one-to-many ' . + 'associations, but ' . $className . '#' . $field . ' is not.'); + } + + public static function illegalInverseIdentifierAssociation(string $className, string $field): self + { + return new self(sprintf( + "An inverse association is not allowed to be identifier in '%s#%s'.", + $className, + $field, + )); + } + + public static function illegalToManyIdentifierAssociation(string $className, string $field): self + { + return new self(sprintf( + "Many-to-many or one-to-many associations are not allowed to be identifier in '%s#%s'.", + $className, + $field, + )); + } + + public static function noInheritanceOnMappedSuperClass(string $className): self + { + return new self("It is not supported to define inheritance information on a mapped superclass '" . $className . "'."); + } + + public static function mappedClassNotPartOfDiscriminatorMap(string $className, string $rootClassName): self + { + return new self( + "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . + "to be properly mapped in the inheritance hierarchy. Alternatively you can make '" . $className . "' an abstract class " . + 'to avoid this exception from occurring.', + ); + } + + public static function lifecycleCallbackMethodNotFound(string $className, string $methodName): self + { + return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); + } + + /** @param class-string $className */ + public static function illegalLifecycleCallbackOnEmbeddedClass(string $event, string $className): self + { + return new self(sprintf( + <<<'EXCEPTION' + Context: Attempt to register lifecycle callback "%s" on embedded class "%s". + Problem: Registering lifecycle callbacks on embedded classes is not allowed. + EXCEPTION, + $event, + $className, + )); + } + + public static function entityListenerClassNotFound(string $listenerName, string $className): self + { + return new self(sprintf('Entity Listener "%s" declared on "%s" not found.', $listenerName, $className)); + } + + public static function entityListenerMethodNotFound(string $listenerName, string $methodName, string $className): self + { + return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName)); + } + + public static function duplicateEntityListener(string $listenerName, string $methodName, string $className): self + { + return new self(sprintf('Entity Listener "%s#%s()" in "%s" was already declared, but it must be declared only once.', $listenerName, $methodName, $className)); + } + + /** @param class-string $className */ + public static function invalidFetchMode(string $className, string $fetchMode): self + { + return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $fetchMode . "'"); + } + + public static function invalidGeneratedMode(int|string $generatedMode): self + { + return new self("Invalid generated mode '" . $generatedMode . "'"); + } + + public static function compositeKeyAssignedIdGeneratorRequired(string $className): self + { + return new self("Entity '" . $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported."); + } + + public static function invalidTargetEntityClass(string $targetEntity, string $sourceEntity, string $associationName): self + { + return new self('The target-entity ' . $targetEntity . " cannot be found in '" . $sourceEntity . '#' . $associationName . "'."); + } + + /** @param string[] $cascades */ + public static function invalidCascadeOption(array $cascades, string $className, string $propertyName): self + { + $cascades = implode(', ', array_map(static fn (string $e): string => "'" . $e . "'", $cascades)); + + return new self(sprintf( + "You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', and 'detach'", + $className, + $propertyName, + $cascades, + )); + } + + public static function missingSequenceName(string $className): self + { + return new self( + sprintf('Missing "sequenceName" attribute for sequence id generator definition on class "%s".', $className), + ); + } + + public static function infiniteEmbeddableNesting(string $className, string $propertyName): self + { + return new self( + sprintf( + 'Infinite nesting detected for embedded property %s::%s. ' . + 'You cannot embed an embeddable from the same type inside an embeddable.', + $className, + $propertyName, + ), + ); + } + + public static function illegalOverrideOfInheritedProperty(string $className, string $propertyName, string $inheritFromClass): self + { + return new self( + sprintf( + 'Overrides are only allowed for fields or associations declared in mapped superclasses or traits. This is not the case for %s::%s, which was inherited from %s.', + $className, + $propertyName, + $inheritFromClass, + ), + ); + } + + public static function invalidIndexConfiguration(string $className, string $indexName): self + { + return new self( + sprintf( + 'Index %s for entity %s should contain columns or fields values, but not both.', + $indexName, + $className, + ), + ); + } + + public static function invalidUniqueConstraintConfiguration(string $className, string $indexName): self + { + return new self( + sprintf( + 'Unique constraint %s for entity %s should contain columns or fields values, but not both.', + $indexName, + $className, + ), + ); + } + + public static function invalidOverrideType(string $expectdType, mixed $givenValue): self + { + return new self(sprintf( + 'Expected %s, but %s was given.', + $expectdType, + get_debug_type($givenValue), + )); + } + + public static function backedEnumTypeRequired(string $className, string $fieldName, string $enumType): self + { + return new self(sprintf( + 'Attempting to map a non-backed enum type %s in entity %s::$%s. Please use backed enums only', + $enumType, + $className, + $fieldName, + )); + } + + public static function nonEnumTypeMapped(string $className, string $fieldName, string $enumType): self + { + return new self(sprintf( + 'Attempting to map non-enum type %s as enum in entity %s::$%s', + $enumType, + $className, + $fieldName, + )); + } + + /** + * @param class-string $className + * @param class-string $enumType + */ + public static function invalidEnumValue( + string $className, + string $fieldName, + string $value, + string $enumType, + ValueError $previous, + ): self { + return new self(sprintf( + <<<'EXCEPTION' +Context: Trying to hydrate enum property "%s::$%s" +Problem: Case "%s" is not listed in enum "%s" +Solution: Either add the case to the enum type or migrate the database column to use another case of the enum +EXCEPTION + , + $className, + $fieldName, + $value, + $enumType, + ), 0, $previous); + } + + /** @param LibXMLError[] $errors */ + public static function fromLibXmlErrors(array $errors): self + { + $formatter = static fn (LibXMLError $error): string => sprintf( + 'libxml error: %s in %s at line %d', + $error->message, + $error->file, + $error->line, + ); + + return new self(implode(PHP_EOL, array_map($formatter, $errors))); + } + + public static function invalidAttributeOnEmbeddable(string $entityName, string $attributeName): self + { + return new self(sprintf( + 'Attribute "%s" on embeddable "%s" is not allowed.', + $attributeName, + $entityName, + )); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/NamingStrategy.php b/vendor/doctrine/orm/src/Mapping/NamingStrategy.php new file mode 100644 index 0000000..afedebe --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/NamingStrategy.php @@ -0,0 +1,71 @@ +, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): static + { + $mapping = parent::fromMappingArray($mappingArray); + + if ($mapping->orphanRemoval && ! $mapping->isCascadeRemove()) { + $mapping->cascade[] = 'remove'; + } + + return $mapping; + } + + /** + * @param mixed[] $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArrayAndName(array $mappingArray, string $name): static + { + $mapping = self::fromMappingArray($mappingArray); + + // OneToMany-side MUST be inverse (must have mappedBy) + if (! isset($mapping->mappedBy)) { + throw MappingException::oneToManyRequiresMappedBy($name, $mapping->fieldName); + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/OneToOne.php b/vendor/doctrine/orm/src/Mapping/OneToOne.php new file mode 100644 index 0000000..1ddf21c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToOne.php @@ -0,0 +1,26 @@ +|null $cascade + * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch + */ + public function __construct( + public readonly string|null $targetEntity = null, + public readonly string|null $mappedBy = null, + public readonly string|null $inversedBy = null, + public readonly array|null $cascade = null, + public readonly string $fetch = 'LAZY', + public readonly bool $orphanRemoval = false, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php new file mode 100644 index 0000000..89c6483 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToOneAssociationMapping.php @@ -0,0 +1,9 @@ + $value */ + public function __construct( + public readonly array $value, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/OwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/OwningSideMapping.php new file mode 100644 index 0000000..ab8b7b2 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OwningSideMapping.php @@ -0,0 +1,28 @@ + */ + public function __sleep(): array + { + $serialized = parent::__sleep(); + + if ($this->inversedBy !== null) { + $serialized[] = 'inversedBy'; + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/PostLoad.php b/vendor/doctrine/orm/src/Mapping/PostLoad.php new file mode 100644 index 0000000..9ce1e5c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PostLoad.php @@ -0,0 +1,12 @@ + + */ + public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array; + + /** + * Gets the column alias. + */ + public function getColumnAlias( + string $columnName, + int $counter, + AbstractPlatform $platform, + ClassMetadata|null $class = null, + ): string; +} diff --git a/vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php b/vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php new file mode 100644 index 0000000..da3d097 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ReflectionEmbeddedProperty.php @@ -0,0 +1,61 @@ +getDeclaringClass()->name, $childProperty->getName()); + } + + public function getValue(object|null $object = null): mixed + { + $embeddedObject = $this->parentProperty->getValue($object); + + if ($embeddedObject === null) { + return null; + } + + return $this->childProperty->getValue($embeddedObject); + } + + public function setValue(mixed $object, mixed $value = null): void + { + $embeddedObject = $this->parentProperty->getValue($object); + + if ($embeddedObject === null) { + $this->instantiator ??= new Instantiator(); + + $embeddedObject = $this->instantiator->instantiate($this->embeddedClass); + + $this->parentProperty->setValue($object, $embeddedObject); + } + + $this->childProperty->setValue($embeddedObject, $value); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php b/vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php new file mode 100644 index 0000000..0ebd978 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ReflectionEnumProperty.php @@ -0,0 +1,87 @@ + $enumType */ + public function __construct( + private readonly ReflectionProperty $originalReflectionProperty, + private readonly string $enumType, + ) { + parent::__construct( + $originalReflectionProperty->class, + $originalReflectionProperty->name, + ); + } + + public function getValue(object|null $object = null): int|string|array|null + { + if ($object === null) { + return null; + } + + $enum = $this->originalReflectionProperty->getValue($object); + + if ($enum === null) { + return null; + } + + if (is_array($enum)) { + return array_map( + static fn (BackedEnum $item): int|string => $item->value, + $enum, + ); + } + + return $enum->value; + } + + /** + * @param object $object + * @param int|string|int[]|string[]|BackedEnum|BackedEnum[]|null $value + */ + public function setValue(mixed $object, mixed $value = null): void + { + if ($value !== null) { + if (is_array($value)) { + $value = array_map(fn (int|string|BackedEnum $item): BackedEnum => $this->initializeEnumValue($object, $item), $value); + } else { + $value = $this->initializeEnumValue($object, $value); + } + } + + $this->originalReflectionProperty->setValue($object, $value); + } + + private function initializeEnumValue(object $object, int|string|BackedEnum $value): BackedEnum + { + if ($value instanceof BackedEnum) { + return $value; + } + + $enumType = $this->enumType; + + try { + return $enumType::from($value); + } catch (ValueError $e) { + throw MappingException::invalidEnumValue( + $object::class, + $this->originalReflectionProperty->name, + (string) $value, + $enumType, + $e, + ); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php b/vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php new file mode 100644 index 0000000..13e9f6d --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ReflectionReadonlyProperty.php @@ -0,0 +1,49 @@ +isReadOnly()) { + throw new InvalidArgumentException('Given property is not readonly.'); + } + + parent::__construct($wrappedProperty->class, $wrappedProperty->name); + } + + public function getValue(object|null $object = null): mixed + { + return $this->wrappedProperty->getValue(...func_get_args()); + } + + public function setValue(mixed $objectOrValue, mixed $value = null): void + { + if (func_num_args() < 2 || $objectOrValue === null || ! $this->isInitialized($objectOrValue)) { + $this->wrappedProperty->setValue(...func_get_args()); + + return; + } + + assert(is_object($objectOrValue)); + + if (parent::getValue($objectOrValue) !== $value) { + throw new LogicException(sprintf('Attempting to change readonly property %s::$%s.', $this->class, $this->name)); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/SequenceGenerator.php b/vendor/doctrine/orm/src/Mapping/SequenceGenerator.php new file mode 100644 index 0000000..6c06e84 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/SequenceGenerator.php @@ -0,0 +1,18 @@ +|null $indexes + * @param array|null $uniqueConstraints + * @param array $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $schema = null, + public readonly array|null $indexes = null, + public readonly array|null $uniqueConstraints = null, + public readonly array $options = [], + ) { + if ($this->indexes !== null) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11357', + 'Providing the property $indexes on %s does not have any effect and will be removed in Doctrine ORM 4.0. Please use the %s attribute instead.', + self::class, + Index::class, + ); + } + + if ($this->uniqueConstraints !== null) { + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/11357', + 'Providing the property $uniqueConstraints on %s does not have any effect and will be removed in Doctrine ORM 4.0. Please use the %s attribute instead.', + self::class, + UniqueConstraint::class, + ); + } + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php new file mode 100644 index 0000000..2e4969c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMapping.php @@ -0,0 +1,16 @@ +indexBy() */ + public function isIndexed(): bool; + + public function indexBy(): string; + + /** @return array */ + public function orderBy(): array; +} diff --git a/vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php new file mode 100644 index 0000000..306880d --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyAssociationMappingImplementation.php @@ -0,0 +1,69 @@ + + */ + public array $orderBy = []; + + /** @return array */ + final public function orderBy(): array + { + return $this->orderBy; + } + + /** @psalm-assert-if-true !null $this->indexBy */ + final public function isIndexed(): bool + { + return $this->indexBy !== null; + } + + final public function indexBy(): string + { + if (! $this->isIndexed()) { + throw new LogicException(sprintf( + 'This mapping is not indexed. Use %s::isIndexed() to check that before calling %s.', + self::class, + __METHOD__, + )); + } + + return $this->indexBy; + } + + /** @return list */ + public function __sleep(): array + { + $serialized = parent::__sleep(); + + if ($this->indexBy !== null) { + $serialized[] = 'indexBy'; + } + + if ($this->orderBy !== []) { + $serialized[] = 'orderBy'; + } + + return $serialized; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php b/vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php new file mode 100644 index 0000000..a092ebe --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyInverseSideMapping.php @@ -0,0 +1,10 @@ +, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * } $mappingArray + */ + public static function fromMappingArrayAndName( + array $mappingArray, + string $name, + ): static { + $mapping = static::fromMappingArray($mappingArray); + + if (isset($mapping->id) && $mapping->id === true) { + throw MappingException::illegalInverseIdentifierAssociation($name, $mapping->fieldName); + } + + if ($mapping->orphanRemoval) { + if (! $mapping->isCascadeRemove()) { + $mapping->cascade[] = 'remove'; + } + + $mapping->unique = null; + } + + return $mapping; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php new file mode 100644 index 0000000..cb85afb --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToOneOwningSideMapping.php @@ -0,0 +1,212 @@ + */ + public array $sourceToTargetKeyColumns = []; + + /** @var array */ + public array $targetToSourceKeyColumns = []; + + /** @var list */ + public array $joinColumns = []; + + /** @var array */ + public array $joinColumnFieldNames = []; + + /** + * @param array $mappingArray + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * joinColumns?: mixed[]|null, + * } $mappingArray + */ + public static function fromMappingArray(array $mappingArray): static + { + $joinColumns = $mappingArray['joinColumns'] ?? []; + unset($mappingArray['joinColumns']); + + $instance = parent::fromMappingArray($mappingArray); + assert($instance->isToOneOwningSide()); + + foreach ($joinColumns as $column) { + $instance->joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + + if ($instance->orphanRemoval) { + if (! $instance->isCascadeRemove()) { + $instance->cascade[] = 'remove'; + } + + $instance->unique = null; + } + + return $instance; + } + + /** + * @param mixed[] $mappingArray + * @param class-string $name + * @psalm-param array{ + * fieldName: string, + * sourceEntity: class-string, + * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, + * joinColumns?: mixed[]|null, + * } $mappingArray + */ + public static function fromMappingArrayAndName( + array $mappingArray, + NamingStrategy $namingStrategy, + string $name, + array|null $table, + bool $isInheritanceTypeSingleTable, + ): static { + if (isset($mappingArray['joinColumns'])) { + foreach ($mappingArray['joinColumns'] as $index => $joinColumn) { + if (empty($joinColumn['name'])) { + $mappingArray['joinColumns'][$index]['name'] = $namingStrategy->joinColumnName($mappingArray['fieldName'], $name); + } + } + } + + $mapping = static::fromMappingArray($mappingArray); + + assert($mapping->isToOneOwningSide()); + if (empty($mapping->joinColumns)) { + // Apply default join column + $mapping->joinColumns = [ + JoinColumnMapping::fromMappingArray([ + 'name' => $namingStrategy->joinColumnName($mapping->fieldName, $name), + 'referencedColumnName' => $namingStrategy->referenceColumnName(), + ]), + ]; + } + + $uniqueConstraintColumns = []; + + foreach ($mapping->joinColumns as $joinColumn) { + if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) { + if (count($mapping->joinColumns) === 1) { + if (empty($mapping->id)) { + $joinColumn->unique = true; + } + } else { + $uniqueConstraintColumns[] = $joinColumn->name; + } + } + + if (empty($joinColumn->referencedColumnName)) { + $joinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); + } + + if ($joinColumn->name[0] === '`') { + $joinColumn->name = trim($joinColumn->name, '`'); + $joinColumn->quoted = true; + } + + if ($joinColumn->referencedColumnName[0] === '`') { + $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`'); + $joinColumn->quoted = true; + } + + $mapping->sourceToTargetKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName; + $mapping->joinColumnFieldNames[$joinColumn->name] = $joinColumn->fieldName ?? $joinColumn->name; + } + + if ($uniqueConstraintColumns) { + if (! $table) { + throw new RuntimeException('ClassMetadata::setTable() has to be called before defining a one to one relationship.'); + } + + $table['uniqueConstraints'][$mapping->fieldName . '_uniq'] = ['columns' => $uniqueConstraintColumns]; + } + + $mapping->targetToSourceKeyColumns = array_flip($mapping->sourceToTargetKeyColumns); + + return $mapping; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if ($offset === 'joinColumns') { + $joinColumns = []; + foreach ($value as $column) { + $joinColumns[] = JoinColumnMapping::fromMappingArray($column); + } + + $this->joinColumns = $joinColumns; + + return; + } + + parent::offsetSet($offset, $value); + } + + /** @return array */ + public function toArray(): array + { + $array = parent::toArray(); + + $joinColumns = []; + foreach ($array['joinColumns'] as $column) { + $joinColumns[] = (array) $column; + } + + $array['joinColumns'] = $joinColumns; + + return $array; + } + + /** @return list */ + public function __sleep(): array + { + return [ + ...parent::__sleep(), + 'joinColumns', + 'joinColumnFieldNames', + 'sourceToTargetKeyColumns', + 'targetToSourceKeyColumns', + ]; + } +} diff --git a/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php b/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php new file mode 100644 index 0000000..2db9e90 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/TypedFieldMapper.php @@ -0,0 +1,20 @@ +, type?: string} $mapping The field mapping to validate & complete. + * + * @return array{fieldName: string, enumType?: class-string, type?: string} The updated mapping. + */ + public function validateAndComplete(array $mapping, ReflectionProperty $field): array; +} diff --git a/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php b/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php new file mode 100644 index 0000000..cedc150 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/UnderscoreNamingStrategy.php @@ -0,0 +1,108 @@ +case; + } + + /** + * Sets string case CASE_LOWER | CASE_UPPER. + * Alphabetic characters converted to lowercase or uppercase. + */ + public function setCase(int $case): void + { + $this->case = $case; + } + + public function classToTableName(string $className): string + { + if (str_contains($className, '\\')) { + $className = substr($className, strrpos($className, '\\') + 1); + } + + return $this->underscore($className); + } + + public function propertyToColumnName(string $propertyName, string $className): string + { + return $this->underscore($propertyName); + } + + public function embeddedFieldToColumnName( + string $propertyName, + string $embeddedColumnName, + string $className, + string $embeddedClassName, + ): string { + return $this->underscore($propertyName) . '_' . $embeddedColumnName; + } + + public function referenceColumnName(): string + { + return $this->case === CASE_UPPER ? 'ID' : 'id'; + } + + public function joinColumnName(string $propertyName, string $className): string + { + return $this->underscore($propertyName) . '_' . $this->referenceColumnName(); + } + + public function joinTableName( + string $sourceEntity, + string $targetEntity, + string $propertyName, + ): string { + return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity); + } + + public function joinKeyColumnName( + string $entityName, + string|null $referencedColumnName, + ): string { + return $this->classToTableName($entityName) . '_' . + ($referencedColumnName ?: $this->referenceColumnName()); + } + + private function underscore(string $string): string + { + $string = preg_replace('/(?<=[a-z0-9])([A-Z])/', '_$1', $string); + + if ($this->case === CASE_UPPER) { + return strtoupper($string); + } + + return strtolower($string); + } +} diff --git a/vendor/doctrine/orm/src/Mapping/UniqueConstraint.php b/vendor/doctrine/orm/src/Mapping/UniqueConstraint.php new file mode 100644 index 0000000..3180be0 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/UniqueConstraint.php @@ -0,0 +1,24 @@ +|null $columns + * @param array|null $fields + * @param array|null $options + */ + public function __construct( + public readonly string|null $name = null, + public readonly array|null $columns = null, + public readonly array|null $fields = null, + public readonly array|null $options = null, + ) { + } +} diff --git a/vendor/doctrine/orm/src/Mapping/Version.php b/vendor/doctrine/orm/src/Mapping/Version.php new file mode 100644 index 0000000..7252e05 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Version.php @@ -0,0 +1,12 @@ +