diff options
| author | polo <ordipolo@gmx.fr> | 2024-08-13 23:45:21 +0200 |
|---|---|---|
| committer | polo <ordipolo@gmx.fr> | 2024-08-13 23:45:21 +0200 |
| commit | bf6655a534a6775d30cafa67bd801276bda1d98d (patch) | |
| tree | c6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Mapping | |
| parent | 94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff) | |
| download | AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.tar.gz AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.tar.bz2 AppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip | |
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Mapping')
100 files changed, 11161 insertions, 0 deletions
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
| 8 | use Doctrine\ORM\Internal\SQLResultCasing; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * ANSI compliant quote strategy, this strategy does not apply any quote. | ||
| 12 | * To use this strategy all mapped tables and columns should be ANSI compliant. | ||
| 13 | */ | ||
| 14 | class AnsiQuoteStrategy implements QuoteStrategy | ||
| 15 | { | ||
| 16 | use SQLResultCasing; | ||
| 17 | |||
| 18 | public function getColumnName( | ||
| 19 | string $fieldName, | ||
| 20 | ClassMetadata $class, | ||
| 21 | AbstractPlatform $platform, | ||
| 22 | ): string { | ||
| 23 | return $class->fieldMappings[$fieldName]->columnName; | ||
| 24 | } | ||
| 25 | |||
| 26 | public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string | ||
| 27 | { | ||
| 28 | return $class->table['name']; | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * {@inheritDoc} | ||
| 33 | */ | ||
| 34 | public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string | ||
| 35 | { | ||
| 36 | return $definition['sequenceName']; | ||
| 37 | } | ||
| 38 | |||
| 39 | public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string | ||
| 40 | { | ||
| 41 | return $joinColumn->name; | ||
| 42 | } | ||
| 43 | |||
| 44 | public function getReferencedJoinColumnName( | ||
| 45 | JoinColumnMapping $joinColumn, | ||
| 46 | ClassMetadata $class, | ||
| 47 | AbstractPlatform $platform, | ||
| 48 | ): string { | ||
| 49 | return $joinColumn->referencedColumnName; | ||
| 50 | } | ||
| 51 | |||
| 52 | public function getJoinTableName( | ||
| 53 | ManyToManyOwningSideMapping $association, | ||
| 54 | ClassMetadata $class, | ||
| 55 | AbstractPlatform $platform, | ||
| 56 | ): string { | ||
| 57 | return $association->joinTable->name; | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * {@inheritDoc} | ||
| 62 | */ | ||
| 63 | public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array | ||
| 64 | { | ||
| 65 | return $class->identifier; | ||
| 66 | } | ||
| 67 | |||
| 68 | public function getColumnAlias( | ||
| 69 | string $columnName, | ||
| 70 | int $counter, | ||
| 71 | AbstractPlatform $platform, | ||
| 72 | ClassMetadata|null $class = null, | ||
| 73 | ): string { | ||
| 74 | return $this->getSQLResultCasing($platform, $columnName . '_' . $counter); | ||
| 75 | } | ||
| 76 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\Deprecations\Deprecation; | ||
| 8 | use InvalidArgumentException; | ||
| 9 | |||
| 10 | use function property_exists; | ||
| 11 | |||
| 12 | /** @internal */ | ||
| 13 | trait ArrayAccessImplementation | ||
| 14 | { | ||
| 15 | /** @param string $offset */ | ||
| 16 | public function offsetExists(mixed $offset): bool | ||
| 17 | { | ||
| 18 | Deprecation::trigger( | ||
| 19 | 'doctrine/orm', | ||
| 20 | 'https://github.com/doctrine/orm/pull/11211', | ||
| 21 | 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', | ||
| 22 | static::class, | ||
| 23 | ); | ||
| 24 | |||
| 25 | return isset($this->$offset); | ||
| 26 | } | ||
| 27 | |||
| 28 | /** @param string $offset */ | ||
| 29 | public function offsetGet(mixed $offset): mixed | ||
| 30 | { | ||
| 31 | Deprecation::trigger( | ||
| 32 | 'doctrine/orm', | ||
| 33 | 'https://github.com/doctrine/orm/pull/11211', | ||
| 34 | 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', | ||
| 35 | static::class, | ||
| 36 | ); | ||
| 37 | |||
| 38 | if (! property_exists($this, $offset)) { | ||
| 39 | throw new InvalidArgumentException('Undefined property: ' . $offset); | ||
| 40 | } | ||
| 41 | |||
| 42 | return $this->$offset; | ||
| 43 | } | ||
| 44 | |||
| 45 | /** @param string $offset */ | ||
| 46 | public function offsetSet(mixed $offset, mixed $value): void | ||
| 47 | { | ||
| 48 | Deprecation::trigger( | ||
| 49 | 'doctrine/orm', | ||
| 50 | 'https://github.com/doctrine/orm/pull/11211', | ||
| 51 | 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', | ||
| 52 | static::class, | ||
| 53 | ); | ||
| 54 | |||
| 55 | $this->$offset = $value; | ||
| 56 | } | ||
| 57 | |||
| 58 | /** @param string $offset */ | ||
| 59 | public function offsetUnset(mixed $offset): void | ||
| 60 | { | ||
| 61 | Deprecation::trigger( | ||
| 62 | 'doctrine/orm', | ||
| 63 | 'https://github.com/doctrine/orm/pull/11211', | ||
| 64 | 'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.', | ||
| 65 | static::class, | ||
| 66 | ); | ||
| 67 | |||
| 68 | $this->$offset = null; | ||
| 69 | } | ||
| 70 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use ArrayAccess; | ||
| 8 | use Exception; | ||
| 9 | use OutOfRangeException; | ||
| 10 | |||
| 11 | use function assert; | ||
| 12 | use function count; | ||
| 13 | use function in_array; | ||
| 14 | use function property_exists; | ||
| 15 | use function sprintf; | ||
| 16 | |||
| 17 | /** @template-implements ArrayAccess<string, mixed> */ | ||
| 18 | abstract class AssociationMapping implements ArrayAccess | ||
| 19 | { | ||
| 20 | /** | ||
| 21 | * The names of persistence operations to cascade on the association. | ||
| 22 | * | ||
| 23 | * @var list<'persist'|'remove'|'detach'|'refresh'|'all'> | ||
| 24 | */ | ||
| 25 | public array $cascade = []; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. | ||
| 29 | * | ||
| 30 | * @var ClassMetadata::FETCH_*|null | ||
| 31 | */ | ||
| 32 | public int|null $fetch = null; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * This is set when the association is inherited by this class from another | ||
| 36 | * (inheritance) parent <em>entity</em> class. The value is the FQCN of the | ||
| 37 | * topmost entity class that contains this association. (If there are | ||
| 38 | * transient classes in the class hierarchy, these are ignored, so the | ||
| 39 | * class property may in fact come from a class further up in the PHP class | ||
| 40 | * hierarchy.) To-many associations initially declared in mapped | ||
| 41 | * superclasses are <em>not</em> considered 'inherited' in the nearest | ||
| 42 | * entity subclasses. | ||
| 43 | * | ||
| 44 | * @var class-string|null | ||
| 45 | */ | ||
| 46 | public string|null $inherited = null; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * This is set when the association does not appear in the current class | ||
| 50 | * for the first time, but is initially declared in another parent | ||
| 51 | * <em>entity or mapped superclass</em>. The value is the FQCN of the | ||
| 52 | * topmost non-transient class that contains association information for | ||
| 53 | * this relationship. | ||
| 54 | * | ||
| 55 | * @var class-string|null | ||
| 56 | */ | ||
| 57 | public string|null $declared = null; | ||
| 58 | |||
| 59 | public array|null $cache = null; | ||
| 60 | |||
| 61 | public bool|null $id = null; | ||
| 62 | |||
| 63 | public bool|null $isOnDeleteCascade = null; | ||
| 64 | |||
| 65 | /** @var class-string|null */ | ||
| 66 | public string|null $originalClass = null; | ||
| 67 | |||
| 68 | public string|null $originalField = null; | ||
| 69 | |||
| 70 | public bool $orphanRemoval = false; | ||
| 71 | |||
| 72 | public bool|null $unique = null; | ||
| 73 | |||
| 74 | /** | ||
| 75 | * @param string $fieldName The name of the field in the entity | ||
| 76 | * the association is mapped to. | ||
| 77 | * @param class-string $sourceEntity The class name of the source entity. | ||
| 78 | * In the case of to-many associations | ||
| 79 | * initially present in mapped | ||
| 80 | * superclasses, the nearest | ||
| 81 | * <em>entity</em> subclasses will be | ||
| 82 | * considered the respective source | ||
| 83 | * entities. | ||
| 84 | * @param class-string $targetEntity The class name of the target entity. | ||
| 85 | * If it is fully-qualified it is used as | ||
| 86 | * is. If it is a simple, unqualified | ||
| 87 | * class name the namespace is assumed to | ||
| 88 | * be the same as the namespace of the | ||
| 89 | * source entity. | ||
| 90 | */ | ||
| 91 | final public function __construct( | ||
| 92 | public readonly string $fieldName, | ||
| 93 | public string $sourceEntity, | ||
| 94 | public readonly string $targetEntity, | ||
| 95 | ) { | ||
| 96 | } | ||
| 97 | |||
| 98 | /** | ||
| 99 | * @param mixed[] $mappingArray | ||
| 100 | * @psalm-param array{ | ||
| 101 | * fieldName: string, | ||
| 102 | * sourceEntity: class-string, | ||
| 103 | * targetEntity: class-string, | ||
| 104 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 105 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 106 | * inherited?: class-string|null, | ||
| 107 | * declared?: class-string|null, | ||
| 108 | * cache?: array<mixed>|null, | ||
| 109 | * id?: bool|null, | ||
| 110 | * isOnDeleteCascade?: bool|null, | ||
| 111 | * originalClass?: class-string|null, | ||
| 112 | * originalField?: string|null, | ||
| 113 | * orphanRemoval?: bool, | ||
| 114 | * unique?: bool|null, | ||
| 115 | * joinTable?: mixed[]|null, | ||
| 116 | * type?: int, | ||
| 117 | * isOwningSide: bool, | ||
| 118 | * } $mappingArray | ||
| 119 | */ | ||
| 120 | public static function fromMappingArray(array $mappingArray): static | ||
| 121 | { | ||
| 122 | unset($mappingArray['isOwningSide'], $mappingArray['type']); | ||
| 123 | $mapping = new static( | ||
| 124 | $mappingArray['fieldName'], | ||
| 125 | $mappingArray['sourceEntity'], | ||
| 126 | $mappingArray['targetEntity'], | ||
| 127 | ); | ||
| 128 | unset($mappingArray['fieldName'], $mappingArray['sourceEntity'], $mappingArray['targetEntity']); | ||
| 129 | |||
| 130 | foreach ($mappingArray as $key => $value) { | ||
| 131 | if ($key === 'joinTable') { | ||
| 132 | assert($mapping instanceof ManyToManyAssociationMapping); | ||
| 133 | |||
| 134 | if ($value === [] || $value === null) { | ||
| 135 | continue; | ||
| 136 | } | ||
| 137 | |||
| 138 | assert($mapping instanceof ManyToManyOwningSideMapping); | ||
| 139 | |||
| 140 | $mapping->joinTable = JoinTableMapping::fromMappingArray($value); | ||
| 141 | |||
| 142 | continue; | ||
| 143 | } | ||
| 144 | |||
| 145 | if (property_exists($mapping, $key)) { | ||
| 146 | $mapping->$key = $value; | ||
| 147 | } else { | ||
| 148 | throw new OutOfRangeException('Unknown property ' . $key . ' on class ' . static::class); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | return $mapping; | ||
| 153 | } | ||
| 154 | |||
| 155 | /** | ||
| 156 | * @psalm-assert-if-true OwningSideMapping $this | ||
| 157 | * @psalm-assert-if-false InverseSideMapping $this | ||
| 158 | */ | ||
| 159 | final public function isOwningSide(): bool | ||
| 160 | { | ||
| 161 | return $this instanceof OwningSideMapping; | ||
| 162 | } | ||
| 163 | |||
| 164 | /** @psalm-assert-if-true ToOneAssociationMapping $this */ | ||
| 165 | final public function isToOne(): bool | ||
| 166 | { | ||
| 167 | return $this instanceof ToOneAssociationMapping; | ||
| 168 | } | ||
| 169 | |||
| 170 | /** @psalm-assert-if-true ToManyAssociationMapping $this */ | ||
| 171 | final public function isToMany(): bool | ||
| 172 | { | ||
| 173 | return $this instanceof ToManyAssociationMapping; | ||
| 174 | } | ||
| 175 | |||
| 176 | /** @psalm-assert-if-true OneToOneOwningSideMapping $this */ | ||
| 177 | final public function isOneToOneOwningSide(): bool | ||
| 178 | { | ||
| 179 | return $this->isOneToOne() && $this->isOwningSide(); | ||
| 180 | } | ||
| 181 | |||
| 182 | /** @psalm-assert-if-true OneToOneOwningSideMapping|ManyToOneAssociationMapping $this */ | ||
| 183 | final public function isToOneOwningSide(): bool | ||
| 184 | { | ||
| 185 | return $this->isToOne() && $this->isOwningSide(); | ||
| 186 | } | ||
| 187 | |||
| 188 | /** @psalm-assert-if-true ManyToManyOwningSideMapping $this */ | ||
| 189 | final public function isManyToManyOwningSide(): bool | ||
| 190 | { | ||
| 191 | return $this instanceof ManyToManyOwningSideMapping; | ||
| 192 | } | ||
| 193 | |||
| 194 | /** @psalm-assert-if-true OneToOneAssociationMapping $this */ | ||
| 195 | final public function isOneToOne(): bool | ||
| 196 | { | ||
| 197 | return $this instanceof OneToOneAssociationMapping; | ||
| 198 | } | ||
| 199 | |||
| 200 | /** @psalm-assert-if-true OneToManyAssociationMapping $this */ | ||
| 201 | final public function isOneToMany(): bool | ||
| 202 | { | ||
| 203 | return $this instanceof OneToManyAssociationMapping; | ||
| 204 | } | ||
| 205 | |||
| 206 | /** @psalm-assert-if-true ManyToOneAssociationMapping $this */ | ||
| 207 | final public function isManyToOne(): bool | ||
| 208 | { | ||
| 209 | return $this instanceof ManyToOneAssociationMapping; | ||
| 210 | } | ||
| 211 | |||
| 212 | /** @psalm-assert-if-true ManyToManyAssociationMapping $this */ | ||
| 213 | final public function isManyToMany(): bool | ||
| 214 | { | ||
| 215 | return $this instanceof ManyToManyAssociationMapping; | ||
| 216 | } | ||
| 217 | |||
| 218 | /** @psalm-assert-if-true ToManyAssociationMapping $this */ | ||
| 219 | final public function isOrdered(): bool | ||
| 220 | { | ||
| 221 | return $this->isToMany() && $this->orderBy() !== []; | ||
| 222 | } | ||
| 223 | |||
| 224 | /** @psalm-assert-if-true ToManyAssociationMapping $this */ | ||
| 225 | public function isIndexed(): bool | ||
| 226 | { | ||
| 227 | return false; | ||
| 228 | } | ||
| 229 | |||
| 230 | final public function type(): int | ||
| 231 | { | ||
| 232 | return match (true) { | ||
| 233 | $this instanceof OneToOneAssociationMapping => ClassMetadata::ONE_TO_ONE, | ||
| 234 | $this instanceof OneToManyAssociationMapping => ClassMetadata::ONE_TO_MANY, | ||
| 235 | $this instanceof ManyToOneAssociationMapping => ClassMetadata::MANY_TO_ONE, | ||
| 236 | $this instanceof ManyToManyAssociationMapping => ClassMetadata::MANY_TO_MANY, | ||
| 237 | default => throw new Exception('Cannot determine type for ' . static::class), | ||
| 238 | }; | ||
| 239 | } | ||
| 240 | |||
| 241 | /** @param string $offset */ | ||
| 242 | public function offsetExists(mixed $offset): bool | ||
| 243 | { | ||
| 244 | return isset($this->$offset) || in_array($offset, ['isOwningSide', 'type'], true); | ||
| 245 | } | ||
| 246 | |||
| 247 | final public function offsetGet(mixed $offset): mixed | ||
| 248 | { | ||
| 249 | return match ($offset) { | ||
| 250 | 'isOwningSide' => $this->isOwningSide(), | ||
| 251 | 'type' => $this->type(), | ||
| 252 | 'isCascadeRemove' => $this->isCascadeRemove(), | ||
| 253 | 'isCascadePersist' => $this->isCascadePersist(), | ||
| 254 | 'isCascadeRefresh' => $this->isCascadeRefresh(), | ||
| 255 | 'isCascadeDetach' => $this->isCascadeDetach(), | ||
| 256 | default => property_exists($this, $offset) ? $this->$offset : throw new OutOfRangeException(sprintf( | ||
| 257 | 'Unknown property "%s" on class %s', | ||
| 258 | $offset, | ||
| 259 | static::class, | ||
| 260 | )), | ||
| 261 | }; | ||
| 262 | } | ||
| 263 | |||
| 264 | public function offsetSet(mixed $offset, mixed $value): void | ||
| 265 | { | ||
| 266 | assert($offset !== null); | ||
| 267 | if (! property_exists($this, $offset)) { | ||
| 268 | throw new OutOfRangeException(sprintf( | ||
| 269 | 'Unknown property "%s" on class %s', | ||
| 270 | $offset, | ||
| 271 | static::class, | ||
| 272 | )); | ||
| 273 | } | ||
| 274 | |||
| 275 | if ($offset === 'joinTable') { | ||
| 276 | $value = JoinTableMapping::fromMappingArray($value); | ||
| 277 | } | ||
| 278 | |||
| 279 | $this->$offset = $value; | ||
| 280 | } | ||
| 281 | |||
| 282 | /** @param string $offset */ | ||
| 283 | public function offsetUnset(mixed $offset): void | ||
| 284 | { | ||
| 285 | if (! property_exists($this, $offset)) { | ||
| 286 | throw new OutOfRangeException(sprintf( | ||
| 287 | 'Unknown property "%s" on class %s', | ||
| 288 | $offset, | ||
| 289 | static::class, | ||
| 290 | )); | ||
| 291 | } | ||
| 292 | |||
| 293 | $this->$offset = null; | ||
| 294 | } | ||
| 295 | |||
| 296 | final public function isCascadeRemove(): bool | ||
| 297 | { | ||
| 298 | return in_array('remove', $this->cascade, true); | ||
| 299 | } | ||
| 300 | |||
| 301 | final public function isCascadePersist(): bool | ||
| 302 | { | ||
| 303 | return in_array('persist', $this->cascade, true); | ||
| 304 | } | ||
| 305 | |||
| 306 | final public function isCascadeRefresh(): bool | ||
| 307 | { | ||
| 308 | return in_array('refresh', $this->cascade, true); | ||
| 309 | } | ||
| 310 | |||
| 311 | final public function isCascadeDetach(): bool | ||
| 312 | { | ||
| 313 | return in_array('detach', $this->cascade, true); | ||
| 314 | } | ||
| 315 | |||
| 316 | /** @return array<string, mixed> */ | ||
| 317 | public function toArray(): array | ||
| 318 | { | ||
| 319 | $array = (array) $this; | ||
| 320 | |||
| 321 | $array['isOwningSide'] = $this->isOwningSide(); | ||
| 322 | $array['type'] = $this->type(); | ||
| 323 | |||
| 324 | return $array; | ||
| 325 | } | ||
| 326 | |||
| 327 | /** @return list<string> */ | ||
| 328 | public function __sleep(): array | ||
| 329 | { | ||
| 330 | $serialized = ['fieldName', 'sourceEntity', 'targetEntity']; | ||
| 331 | |||
| 332 | if (count($this->cascade) > 0) { | ||
| 333 | $serialized[] = 'cascade'; | ||
| 334 | } | ||
| 335 | |||
| 336 | foreach ( | ||
| 337 | [ | ||
| 338 | 'fetch', | ||
| 339 | 'inherited', | ||
| 340 | 'declared', | ||
| 341 | 'cache', | ||
| 342 | 'originalClass', | ||
| 343 | 'originalField', | ||
| 344 | ] as $stringOrArrayProperty | ||
| 345 | ) { | ||
| 346 | if ($this->$stringOrArrayProperty !== null) { | ||
| 347 | $serialized[] = $stringOrArrayProperty; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | foreach (['id', 'orphanRemoval', 'isOnDeleteCascade', 'unique'] as $boolProperty) { | ||
| 352 | if ($this->$boolProperty) { | ||
| 353 | $serialized[] = $boolProperty; | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | return $serialized; | ||
| 358 | } | ||
| 359 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | /** This attribute is used to override association mapping of property for an entity relationship. */ | ||
| 8 | final class AssociationOverride implements MappingAttribute | ||
| 9 | { | ||
| 10 | /** | ||
| 11 | * The join column that is being mapped to the persistent attribute. | ||
| 12 | * | ||
| 13 | * @var array<JoinColumn>|null | ||
| 14 | */ | ||
| 15 | public readonly array|null $joinColumns; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * The join column that is being mapped to the persistent attribute. | ||
| 19 | * | ||
| 20 | * @var array<JoinColumn>|null | ||
| 21 | */ | ||
| 22 | public readonly array|null $inverseJoinColumns; | ||
| 23 | |||
| 24 | /** | ||
| 25 | * @param string $name The name of the relationship property whose mapping is being overridden. | ||
| 26 | * @param JoinColumn|array<JoinColumn> $joinColumns | ||
| 27 | * @param JoinColumn|array<JoinColumn> $inverseJoinColumns | ||
| 28 | * @param JoinTable|null $joinTable The join table that maps the relationship. | ||
| 29 | * @param string|null $inversedBy The name of the association-field on the inverse-side. | ||
| 30 | * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch | ||
| 31 | */ | ||
| 32 | public function __construct( | ||
| 33 | public readonly string $name, | ||
| 34 | array|JoinColumn|null $joinColumns = null, | ||
| 35 | array|JoinColumn|null $inverseJoinColumns = null, | ||
| 36 | public readonly JoinTable|null $joinTable = null, | ||
| 37 | public readonly string|null $inversedBy = null, | ||
| 38 | public readonly string|null $fetch = null, | ||
| 39 | ) { | ||
| 40 | if ($joinColumns instanceof JoinColumn) { | ||
| 41 | $joinColumns = [$joinColumns]; | ||
| 42 | } | ||
| 43 | |||
| 44 | if ($inverseJoinColumns instanceof JoinColumn) { | ||
| 45 | $inverseJoinColumns = [$inverseJoinColumns]; | ||
| 46 | } | ||
| 47 | |||
| 48 | $this->joinColumns = $joinColumns; | ||
| 49 | $this->inverseJoinColumns = $inverseJoinColumns; | ||
| 50 | } | ||
| 51 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | use function array_values; | ||
| 10 | use function is_array; | ||
| 11 | |||
| 12 | /** This attribute is used to override association mappings of relationship properties. */ | ||
| 13 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 14 | final class AssociationOverrides implements MappingAttribute | ||
| 15 | { | ||
| 16 | /** | ||
| 17 | * Mapping overrides of relationship properties. | ||
| 18 | * | ||
| 19 | * @var list<AssociationOverride> | ||
| 20 | */ | ||
| 21 | public readonly array $overrides; | ||
| 22 | |||
| 23 | /** @param array<AssociationOverride>|AssociationOverride $overrides */ | ||
| 24 | public function __construct(array|AssociationOverride $overrides) | ||
| 25 | { | ||
| 26 | if (! is_array($overrides)) { | ||
| 27 | $overrides = [$overrides]; | ||
| 28 | } | ||
| 29 | |||
| 30 | foreach ($overrides as $override) { | ||
| 31 | if (! ($override instanceof AssociationOverride)) { | ||
| 32 | throw MappingException::invalidOverrideType('AssociationOverride', $override); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | $this->overrides = array_values($overrides); | ||
| 37 | } | ||
| 38 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | /** This attribute is used to override the mapping of a entity property. */ | ||
| 8 | final class AttributeOverride implements MappingAttribute | ||
| 9 | { | ||
| 10 | public function __construct( | ||
| 11 | public string $name, | ||
| 12 | public Column $column, | ||
| 13 | ) { | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/AttributeOverrides.php b/vendor/doctrine/orm/src/Mapping/AttributeOverrides.php new file mode 100644 index 0000000..9c7b9db --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/AttributeOverrides.php | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | use function array_values; | ||
| 10 | use function is_array; | ||
| 11 | |||
| 12 | /** This attribute is used to override the mapping of a entity property. */ | ||
| 13 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 14 | final class AttributeOverrides implements MappingAttribute | ||
| 15 | { | ||
| 16 | /** | ||
| 17 | * One or more field or property mapping overrides. | ||
| 18 | * | ||
| 19 | * @var list<AttributeOverride> | ||
| 20 | */ | ||
| 21 | public readonly array $overrides; | ||
| 22 | |||
| 23 | /** @param array<AttributeOverride>|AttributeOverride $overrides */ | ||
| 24 | public function __construct(array|AttributeOverride $overrides) | ||
| 25 | { | ||
| 26 | if (! is_array($overrides)) { | ||
| 27 | $overrides = [$overrides]; | ||
| 28 | } | ||
| 29 | |||
| 30 | foreach ($overrides as $override) { | ||
| 31 | if (! ($override instanceof AttributeOverride)) { | ||
| 32 | throw MappingException::invalidOverrideType('AttributeOverride', $override); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | $this->overrides = array_values($overrides); | ||
| 37 | } | ||
| 38 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 8 | use InvalidArgumentException; | ||
| 9 | |||
| 10 | class AssociationBuilder | ||
| 11 | { | ||
| 12 | /** @var mixed[]|null */ | ||
| 13 | protected array|null $joinColumns = null; | ||
| 14 | |||
| 15 | /** @param mixed[] $mapping */ | ||
| 16 | public function __construct( | ||
| 17 | protected readonly ClassMetadataBuilder $builder, | ||
| 18 | protected array $mapping, | ||
| 19 | protected readonly int $type, | ||
| 20 | ) { | ||
| 21 | } | ||
| 22 | |||
| 23 | /** @return $this */ | ||
| 24 | public function mappedBy(string $fieldName): static | ||
| 25 | { | ||
| 26 | $this->mapping['mappedBy'] = $fieldName; | ||
| 27 | |||
| 28 | return $this; | ||
| 29 | } | ||
| 30 | |||
| 31 | /** @return $this */ | ||
| 32 | public function inversedBy(string $fieldName): static | ||
| 33 | { | ||
| 34 | $this->mapping['inversedBy'] = $fieldName; | ||
| 35 | |||
| 36 | return $this; | ||
| 37 | } | ||
| 38 | |||
| 39 | /** @return $this */ | ||
| 40 | public function cascadeAll(): static | ||
| 41 | { | ||
| 42 | $this->mapping['cascade'] = ['ALL']; | ||
| 43 | |||
| 44 | return $this; | ||
| 45 | } | ||
| 46 | |||
| 47 | /** @return $this */ | ||
| 48 | public function cascadePersist(): static | ||
| 49 | { | ||
| 50 | $this->mapping['cascade'][] = 'persist'; | ||
| 51 | |||
| 52 | return $this; | ||
| 53 | } | ||
| 54 | |||
| 55 | /** @return $this */ | ||
| 56 | public function cascadeRemove(): static | ||
| 57 | { | ||
| 58 | $this->mapping['cascade'][] = 'remove'; | ||
| 59 | |||
| 60 | return $this; | ||
| 61 | } | ||
| 62 | |||
| 63 | /** @return $this */ | ||
| 64 | public function cascadeDetach(): static | ||
| 65 | { | ||
| 66 | $this->mapping['cascade'][] = 'detach'; | ||
| 67 | |||
| 68 | return $this; | ||
| 69 | } | ||
| 70 | |||
| 71 | /** @return $this */ | ||
| 72 | public function cascadeRefresh(): static | ||
| 73 | { | ||
| 74 | $this->mapping['cascade'][] = 'refresh'; | ||
| 75 | |||
| 76 | return $this; | ||
| 77 | } | ||
| 78 | |||
| 79 | /** @return $this */ | ||
| 80 | public function fetchExtraLazy(): static | ||
| 81 | { | ||
| 82 | $this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; | ||
| 83 | |||
| 84 | return $this; | ||
| 85 | } | ||
| 86 | |||
| 87 | /** @return $this */ | ||
| 88 | public function fetchEager(): static | ||
| 89 | { | ||
| 90 | $this->mapping['fetch'] = ClassMetadata::FETCH_EAGER; | ||
| 91 | |||
| 92 | return $this; | ||
| 93 | } | ||
| 94 | |||
| 95 | /** @return $this */ | ||
| 96 | public function fetchLazy(): static | ||
| 97 | { | ||
| 98 | $this->mapping['fetch'] = ClassMetadata::FETCH_LAZY; | ||
| 99 | |||
| 100 | return $this; | ||
| 101 | } | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Add Join Columns. | ||
| 105 | * | ||
| 106 | * @return $this | ||
| 107 | */ | ||
| 108 | public function addJoinColumn( | ||
| 109 | string $columnName, | ||
| 110 | string $referencedColumnName, | ||
| 111 | bool $nullable = true, | ||
| 112 | bool $unique = false, | ||
| 113 | string|null $onDelete = null, | ||
| 114 | string|null $columnDef = null, | ||
| 115 | ): static { | ||
| 116 | $this->joinColumns[] = [ | ||
| 117 | 'name' => $columnName, | ||
| 118 | 'referencedColumnName' => $referencedColumnName, | ||
| 119 | 'nullable' => $nullable, | ||
| 120 | 'unique' => $unique, | ||
| 121 | 'onDelete' => $onDelete, | ||
| 122 | 'columnDefinition' => $columnDef, | ||
| 123 | ]; | ||
| 124 | |||
| 125 | return $this; | ||
| 126 | } | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Sets field as primary key. | ||
| 130 | * | ||
| 131 | * @return $this | ||
| 132 | */ | ||
| 133 | public function makePrimaryKey(): static | ||
| 134 | { | ||
| 135 | $this->mapping['id'] = true; | ||
| 136 | |||
| 137 | return $this; | ||
| 138 | } | ||
| 139 | |||
| 140 | /** | ||
| 141 | * Removes orphan entities when detached from their parent. | ||
| 142 | * | ||
| 143 | * @return $this | ||
| 144 | */ | ||
| 145 | public function orphanRemoval(): static | ||
| 146 | { | ||
| 147 | $this->mapping['orphanRemoval'] = true; | ||
| 148 | |||
| 149 | return $this; | ||
| 150 | } | ||
| 151 | |||
| 152 | /** @throws InvalidArgumentException */ | ||
| 153 | public function build(): ClassMetadataBuilder | ||
| 154 | { | ||
| 155 | $mapping = $this->mapping; | ||
| 156 | if ($this->joinColumns) { | ||
| 157 | $mapping['joinColumns'] = $this->joinColumns; | ||
| 158 | } | ||
| 159 | |||
| 160 | $cm = $this->builder->getClassMetadata(); | ||
| 161 | if ($this->type === ClassMetadata::MANY_TO_ONE) { | ||
| 162 | $cm->mapManyToOne($mapping); | ||
| 163 | } elseif ($this->type === ClassMetadata::ONE_TO_ONE) { | ||
| 164 | $cm->mapOneToOne($mapping); | ||
| 165 | } else { | ||
| 166 | throw new InvalidArgumentException('Type should be a ToOne Association here'); | ||
| 167 | } | ||
| 168 | |||
| 169 | return $this->builder; | ||
| 170 | } | ||
| 171 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Builder Object for ClassMetadata | ||
| 12 | * | ||
| 13 | * @link www.doctrine-project.com | ||
| 14 | */ | ||
| 15 | class ClassMetadataBuilder | ||
| 16 | { | ||
| 17 | public function __construct( | ||
| 18 | private readonly ClassMetadata $cm, | ||
| 19 | ) { | ||
| 20 | } | ||
| 21 | |||
| 22 | public function getClassMetadata(): ClassMetadata | ||
| 23 | { | ||
| 24 | return $this->cm; | ||
| 25 | } | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Marks the class as mapped superclass. | ||
| 29 | * | ||
| 30 | * @return $this | ||
| 31 | */ | ||
| 32 | public function setMappedSuperClass(): static | ||
| 33 | { | ||
| 34 | $this->cm->isMappedSuperclass = true; | ||
| 35 | $this->cm->isEmbeddedClass = false; | ||
| 36 | |||
| 37 | return $this; | ||
| 38 | } | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Marks the class as embeddable. | ||
| 42 | * | ||
| 43 | * @return $this | ||
| 44 | */ | ||
| 45 | public function setEmbeddable(): static | ||
| 46 | { | ||
| 47 | $this->cm->isEmbeddedClass = true; | ||
| 48 | $this->cm->isMappedSuperclass = false; | ||
| 49 | |||
| 50 | return $this; | ||
| 51 | } | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Adds and embedded class | ||
| 55 | * | ||
| 56 | * @param class-string $class | ||
| 57 | * | ||
| 58 | * @return $this | ||
| 59 | */ | ||
| 60 | public function addEmbedded(string $fieldName, string $class, string|false|null $columnPrefix = null): static | ||
| 61 | { | ||
| 62 | $this->cm->mapEmbedded( | ||
| 63 | [ | ||
| 64 | 'fieldName' => $fieldName, | ||
| 65 | 'class' => $class, | ||
| 66 | 'columnPrefix' => $columnPrefix, | ||
| 67 | ], | ||
| 68 | ); | ||
| 69 | |||
| 70 | return $this; | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Sets custom Repository class name. | ||
| 75 | * | ||
| 76 | * @return $this | ||
| 77 | */ | ||
| 78 | public function setCustomRepositoryClass(string $repositoryClassName): static | ||
| 79 | { | ||
| 80 | $this->cm->setCustomRepositoryClass($repositoryClassName); | ||
| 81 | |||
| 82 | return $this; | ||
| 83 | } | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Marks class read only. | ||
| 87 | * | ||
| 88 | * @return $this | ||
| 89 | */ | ||
| 90 | public function setReadOnly(): static | ||
| 91 | { | ||
| 92 | $this->cm->markReadOnly(); | ||
| 93 | |||
| 94 | return $this; | ||
| 95 | } | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Sets the table name. | ||
| 99 | * | ||
| 100 | * @return $this | ||
| 101 | */ | ||
| 102 | public function setTable(string $name): static | ||
| 103 | { | ||
| 104 | $this->cm->setPrimaryTable(['name' => $name]); | ||
| 105 | |||
| 106 | return $this; | ||
| 107 | } | ||
| 108 | |||
| 109 | /** | ||
| 110 | * Adds Index. | ||
| 111 | * | ||
| 112 | * @psalm-param list<string> $columns | ||
| 113 | * | ||
| 114 | * @return $this | ||
| 115 | */ | ||
| 116 | public function addIndex(array $columns, string $name): static | ||
| 117 | { | ||
| 118 | if (! isset($this->cm->table['indexes'])) { | ||
| 119 | $this->cm->table['indexes'] = []; | ||
| 120 | } | ||
| 121 | |||
| 122 | $this->cm->table['indexes'][$name] = ['columns' => $columns]; | ||
| 123 | |||
| 124 | return $this; | ||
| 125 | } | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Adds Unique Constraint. | ||
| 129 | * | ||
| 130 | * @psalm-param list<string> $columns | ||
| 131 | * | ||
| 132 | * @return $this | ||
| 133 | */ | ||
| 134 | public function addUniqueConstraint(array $columns, string $name): static | ||
| 135 | { | ||
| 136 | if (! isset($this->cm->table['uniqueConstraints'])) { | ||
| 137 | $this->cm->table['uniqueConstraints'] = []; | ||
| 138 | } | ||
| 139 | |||
| 140 | $this->cm->table['uniqueConstraints'][$name] = ['columns' => $columns]; | ||
| 141 | |||
| 142 | return $this; | ||
| 143 | } | ||
| 144 | |||
| 145 | /** | ||
| 146 | * Sets class as root of a joined table inheritance hierarchy. | ||
| 147 | * | ||
| 148 | * @return $this | ||
| 149 | */ | ||
| 150 | public function setJoinedTableInheritance(): static | ||
| 151 | { | ||
| 152 | $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED); | ||
| 153 | |||
| 154 | return $this; | ||
| 155 | } | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Sets class as root of a single table inheritance hierarchy. | ||
| 159 | * | ||
| 160 | * @return $this | ||
| 161 | */ | ||
| 162 | public function setSingleTableInheritance(): static | ||
| 163 | { | ||
| 164 | $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); | ||
| 165 | |||
| 166 | return $this; | ||
| 167 | } | ||
| 168 | |||
| 169 | /** | ||
| 170 | * Sets the discriminator column details. | ||
| 171 | * | ||
| 172 | * @psalm-param class-string<BackedEnum>|null $enumType | ||
| 173 | * @psalm-param array<string, mixed> $options | ||
| 174 | * | ||
| 175 | * @return $this | ||
| 176 | */ | ||
| 177 | public function setDiscriminatorColumn( | ||
| 178 | string $name, | ||
| 179 | string $type = 'string', | ||
| 180 | int $length = 255, | ||
| 181 | string|null $columnDefinition = null, | ||
| 182 | string|null $enumType = null, | ||
| 183 | array $options = [], | ||
| 184 | ): static { | ||
| 185 | $this->cm->setDiscriminatorColumn( | ||
| 186 | [ | ||
| 187 | 'name' => $name, | ||
| 188 | 'type' => $type, | ||
| 189 | 'length' => $length, | ||
| 190 | 'columnDefinition' => $columnDefinition, | ||
| 191 | 'enumType' => $enumType, | ||
| 192 | 'options' => $options, | ||
| 193 | ], | ||
| 194 | ); | ||
| 195 | |||
| 196 | return $this; | ||
| 197 | } | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Adds a subclass to this inheritance hierarchy. | ||
| 201 | * | ||
| 202 | * @return $this | ||
| 203 | */ | ||
| 204 | public function addDiscriminatorMapClass(string $name, string $class): static | ||
| 205 | { | ||
| 206 | $this->cm->addDiscriminatorMapClass($name, $class); | ||
| 207 | |||
| 208 | return $this; | ||
| 209 | } | ||
| 210 | |||
| 211 | /** | ||
| 212 | * Sets deferred explicit change tracking policy. | ||
| 213 | * | ||
| 214 | * @return $this | ||
| 215 | */ | ||
| 216 | public function setChangeTrackingPolicyDeferredExplicit(): static | ||
| 217 | { | ||
| 218 | $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); | ||
| 219 | |||
| 220 | return $this; | ||
| 221 | } | ||
| 222 | |||
| 223 | /** | ||
| 224 | * Adds lifecycle event. | ||
| 225 | * | ||
| 226 | * @return $this | ||
| 227 | */ | ||
| 228 | public function addLifecycleEvent(string $methodName, string $event): static | ||
| 229 | { | ||
| 230 | $this->cm->addLifecycleCallback($methodName, $event); | ||
| 231 | |||
| 232 | return $this; | ||
| 233 | } | ||
| 234 | |||
| 235 | /** | ||
| 236 | * Adds Field. | ||
| 237 | * | ||
| 238 | * @psalm-param array<string, mixed> $mapping | ||
| 239 | * | ||
| 240 | * @return $this | ||
| 241 | */ | ||
| 242 | public function addField(string $name, string $type, array $mapping = []): static | ||
| 243 | { | ||
| 244 | $mapping['fieldName'] = $name; | ||
| 245 | $mapping['type'] = $type; | ||
| 246 | |||
| 247 | $this->cm->mapField($mapping); | ||
| 248 | |||
| 249 | return $this; | ||
| 250 | } | ||
| 251 | |||
| 252 | /** | ||
| 253 | * Creates a field builder. | ||
| 254 | */ | ||
| 255 | public function createField(string $name, string $type): FieldBuilder | ||
| 256 | { | ||
| 257 | return new FieldBuilder( | ||
| 258 | $this, | ||
| 259 | [ | ||
| 260 | 'fieldName' => $name, | ||
| 261 | 'type' => $type, | ||
| 262 | ], | ||
| 263 | ); | ||
| 264 | } | ||
| 265 | |||
| 266 | /** | ||
| 267 | * Creates an embedded builder. | ||
| 268 | */ | ||
| 269 | public function createEmbedded(string $fieldName, string $class): EmbeddedBuilder | ||
| 270 | { | ||
| 271 | return new EmbeddedBuilder( | ||
| 272 | $this, | ||
| 273 | [ | ||
| 274 | 'fieldName' => $fieldName, | ||
| 275 | 'class' => $class, | ||
| 276 | 'columnPrefix' => null, | ||
| 277 | ], | ||
| 278 | ); | ||
| 279 | } | ||
| 280 | |||
| 281 | /** | ||
| 282 | * Adds a simple many to one association, optionally with the inversed by field. | ||
| 283 | */ | ||
| 284 | public function addManyToOne( | ||
| 285 | string $name, | ||
| 286 | string $targetEntity, | ||
| 287 | string|null $inversedBy = null, | ||
| 288 | ): ClassMetadataBuilder { | ||
| 289 | $builder = $this->createManyToOne($name, $targetEntity); | ||
| 290 | |||
| 291 | if ($inversedBy !== null) { | ||
| 292 | $builder->inversedBy($inversedBy); | ||
| 293 | } | ||
| 294 | |||
| 295 | return $builder->build(); | ||
| 296 | } | ||
| 297 | |||
| 298 | /** | ||
| 299 | * Creates a ManyToOne Association Builder. | ||
| 300 | * | ||
| 301 | * Note: This method does not add the association, you have to call build() on the AssociationBuilder. | ||
| 302 | */ | ||
| 303 | public function createManyToOne(string $name, string $targetEntity): AssociationBuilder | ||
| 304 | { | ||
| 305 | return new AssociationBuilder( | ||
| 306 | $this, | ||
| 307 | [ | ||
| 308 | 'fieldName' => $name, | ||
| 309 | 'targetEntity' => $targetEntity, | ||
| 310 | ], | ||
| 311 | ClassMetadata::MANY_TO_ONE, | ||
| 312 | ); | ||
| 313 | } | ||
| 314 | |||
| 315 | /** | ||
| 316 | * Creates a OneToOne Association Builder. | ||
| 317 | */ | ||
| 318 | public function createOneToOne(string $name, string $targetEntity): AssociationBuilder | ||
| 319 | { | ||
| 320 | return new AssociationBuilder( | ||
| 321 | $this, | ||
| 322 | [ | ||
| 323 | 'fieldName' => $name, | ||
| 324 | 'targetEntity' => $targetEntity, | ||
| 325 | ], | ||
| 326 | ClassMetadata::ONE_TO_ONE, | ||
| 327 | ); | ||
| 328 | } | ||
| 329 | |||
| 330 | /** | ||
| 331 | * Adds simple inverse one-to-one association. | ||
| 332 | */ | ||
| 333 | public function addInverseOneToOne(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder | ||
| 334 | { | ||
| 335 | $builder = $this->createOneToOne($name, $targetEntity); | ||
| 336 | $builder->mappedBy($mappedBy); | ||
| 337 | |||
| 338 | return $builder->build(); | ||
| 339 | } | ||
| 340 | |||
| 341 | /** | ||
| 342 | * Adds simple owning one-to-one association. | ||
| 343 | */ | ||
| 344 | public function addOwningOneToOne( | ||
| 345 | string $name, | ||
| 346 | string $targetEntity, | ||
| 347 | string|null $inversedBy = null, | ||
| 348 | ): ClassMetadataBuilder { | ||
| 349 | $builder = $this->createOneToOne($name, $targetEntity); | ||
| 350 | |||
| 351 | if ($inversedBy !== null) { | ||
| 352 | $builder->inversedBy($inversedBy); | ||
| 353 | } | ||
| 354 | |||
| 355 | return $builder->build(); | ||
| 356 | } | ||
| 357 | |||
| 358 | /** | ||
| 359 | * Creates a ManyToMany Association Builder. | ||
| 360 | */ | ||
| 361 | public function createManyToMany(string $name, string $targetEntity): ManyToManyAssociationBuilder | ||
| 362 | { | ||
| 363 | return new ManyToManyAssociationBuilder( | ||
| 364 | $this, | ||
| 365 | [ | ||
| 366 | 'fieldName' => $name, | ||
| 367 | 'targetEntity' => $targetEntity, | ||
| 368 | ], | ||
| 369 | ClassMetadata::MANY_TO_MANY, | ||
| 370 | ); | ||
| 371 | } | ||
| 372 | |||
| 373 | /** | ||
| 374 | * Adds a simple owning many to many association. | ||
| 375 | */ | ||
| 376 | public function addOwningManyToMany( | ||
| 377 | string $name, | ||
| 378 | string $targetEntity, | ||
| 379 | string|null $inversedBy = null, | ||
| 380 | ): ClassMetadataBuilder { | ||
| 381 | $builder = $this->createManyToMany($name, $targetEntity); | ||
| 382 | |||
| 383 | if ($inversedBy !== null) { | ||
| 384 | $builder->inversedBy($inversedBy); | ||
| 385 | } | ||
| 386 | |||
| 387 | return $builder->build(); | ||
| 388 | } | ||
| 389 | |||
| 390 | /** | ||
| 391 | * Adds a simple inverse many to many association. | ||
| 392 | */ | ||
| 393 | public function addInverseManyToMany(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder | ||
| 394 | { | ||
| 395 | $builder = $this->createManyToMany($name, $targetEntity); | ||
| 396 | $builder->mappedBy($mappedBy); | ||
| 397 | |||
| 398 | return $builder->build(); | ||
| 399 | } | ||
| 400 | |||
| 401 | /** | ||
| 402 | * Creates a one to many association builder. | ||
| 403 | */ | ||
| 404 | public function createOneToMany(string $name, string $targetEntity): OneToManyAssociationBuilder | ||
| 405 | { | ||
| 406 | return new OneToManyAssociationBuilder( | ||
| 407 | $this, | ||
| 408 | [ | ||
| 409 | 'fieldName' => $name, | ||
| 410 | 'targetEntity' => $targetEntity, | ||
| 411 | ], | ||
| 412 | ClassMetadata::ONE_TO_MANY, | ||
| 413 | ); | ||
| 414 | } | ||
| 415 | |||
| 416 | /** | ||
| 417 | * Adds simple OneToMany association. | ||
| 418 | */ | ||
| 419 | public function addOneToMany(string $name, string $targetEntity, string $mappedBy): ClassMetadataBuilder | ||
| 420 | { | ||
| 421 | $builder = $this->createOneToMany($name, $targetEntity); | ||
| 422 | $builder->mappedBy($mappedBy); | ||
| 423 | |||
| 424 | return $builder->build(); | ||
| 425 | } | ||
| 426 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Embedded Builder | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.com | ||
| 11 | */ | ||
| 12 | class EmbeddedBuilder | ||
| 13 | { | ||
| 14 | /** @param mixed[] $mapping */ | ||
| 15 | public function __construct( | ||
| 16 | private readonly ClassMetadataBuilder $builder, | ||
| 17 | private array $mapping, | ||
| 18 | ) { | ||
| 19 | } | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Sets the column prefix for all of the embedded columns. | ||
| 23 | * | ||
| 24 | * @return $this | ||
| 25 | */ | ||
| 26 | public function setColumnPrefix(string $columnPrefix): static | ||
| 27 | { | ||
| 28 | $this->mapping['columnPrefix'] = $columnPrefix; | ||
| 29 | |||
| 30 | return $this; | ||
| 31 | } | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Finalizes this embeddable and attach it to the ClassMetadata. | ||
| 35 | * | ||
| 36 | * Without this call an EmbeddedBuilder has no effect on the ClassMetadata. | ||
| 37 | */ | ||
| 38 | public function build(): ClassMetadataBuilder | ||
| 39 | { | ||
| 40 | $cm = $this->builder->getClassMetadata(); | ||
| 41 | |||
| 42 | $cm->mapEmbedded($this->mapping); | ||
| 43 | |||
| 44 | return $this->builder; | ||
| 45 | } | ||
| 46 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Events; | ||
| 8 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 9 | use Doctrine\ORM\Mapping\MappingException; | ||
| 10 | |||
| 11 | use function class_exists; | ||
| 12 | use function get_class_methods; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Builder for entity listeners. | ||
| 16 | */ | ||
| 17 | class EntityListenerBuilder | ||
| 18 | { | ||
| 19 | /** Hash-map to handle event names. */ | ||
| 20 | private const EVENTS = [ | ||
| 21 | Events::preRemove => true, | ||
| 22 | Events::postRemove => true, | ||
| 23 | Events::prePersist => true, | ||
| 24 | Events::postPersist => true, | ||
| 25 | Events::preUpdate => true, | ||
| 26 | Events::postUpdate => true, | ||
| 27 | Events::postLoad => true, | ||
| 28 | Events::preFlush => true, | ||
| 29 | ]; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Lookup the entity class to find methods that match to event lifecycle names | ||
| 33 | * | ||
| 34 | * @param ClassMetadata $metadata The entity metadata. | ||
| 35 | * @param string $className The listener class name. | ||
| 36 | * | ||
| 37 | * @throws MappingException When the listener class not found. | ||
| 38 | */ | ||
| 39 | public static function bindEntityListener(ClassMetadata $metadata, string $className): void | ||
| 40 | { | ||
| 41 | $class = $metadata->fullyQualifiedClassName($className); | ||
| 42 | |||
| 43 | if (! class_exists($class)) { | ||
| 44 | throw MappingException::entityListenerClassNotFound($class, $className); | ||
| 45 | } | ||
| 46 | |||
| 47 | foreach (get_class_methods($class) as $method) { | ||
| 48 | if (! isset(self::EVENTS[$method])) { | ||
| 49 | continue; | ||
| 50 | } | ||
| 51 | |||
| 52 | $metadata->addEntityListener($method, $class, $method); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | use function constant; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Field Builder | ||
| 11 | * | ||
| 12 | * @link www.doctrine-project.com | ||
| 13 | */ | ||
| 14 | class FieldBuilder | ||
| 15 | { | ||
| 16 | private bool $version = false; | ||
| 17 | private string|null $generatedValue = null; | ||
| 18 | |||
| 19 | /** @var mixed[]|null */ | ||
| 20 | private array|null $sequenceDef = null; | ||
| 21 | |||
| 22 | private string|null $customIdGenerator = null; | ||
| 23 | |||
| 24 | /** @param mixed[] $mapping */ | ||
| 25 | public function __construct( | ||
| 26 | private readonly ClassMetadataBuilder $builder, | ||
| 27 | private array $mapping, | ||
| 28 | ) { | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Sets length. | ||
| 33 | * | ||
| 34 | * @return $this | ||
| 35 | */ | ||
| 36 | public function length(int $length): static | ||
| 37 | { | ||
| 38 | $this->mapping['length'] = $length; | ||
| 39 | |||
| 40 | return $this; | ||
| 41 | } | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Sets nullable. | ||
| 45 | * | ||
| 46 | * @return $this | ||
| 47 | */ | ||
| 48 | public function nullable(bool $flag = true): static | ||
| 49 | { | ||
| 50 | $this->mapping['nullable'] = $flag; | ||
| 51 | |||
| 52 | return $this; | ||
| 53 | } | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Sets Unique. | ||
| 57 | * | ||
| 58 | * @return $this | ||
| 59 | */ | ||
| 60 | public function unique(bool $flag = true): static | ||
| 61 | { | ||
| 62 | $this->mapping['unique'] = $flag; | ||
| 63 | |||
| 64 | return $this; | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Sets column name. | ||
| 69 | * | ||
| 70 | * @return $this | ||
| 71 | */ | ||
| 72 | public function columnName(string $name): static | ||
| 73 | { | ||
| 74 | $this->mapping['columnName'] = $name; | ||
| 75 | |||
| 76 | return $this; | ||
| 77 | } | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Sets Precision. | ||
| 81 | * | ||
| 82 | * @return $this | ||
| 83 | */ | ||
| 84 | public function precision(int $p): static | ||
| 85 | { | ||
| 86 | $this->mapping['precision'] = $p; | ||
| 87 | |||
| 88 | return $this; | ||
| 89 | } | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Sets insertable. | ||
| 93 | * | ||
| 94 | * @return $this | ||
| 95 | */ | ||
| 96 | public function insertable(bool $flag = true): self | ||
| 97 | { | ||
| 98 | if (! $flag) { | ||
| 99 | $this->mapping['notInsertable'] = true; | ||
| 100 | } | ||
| 101 | |||
| 102 | return $this; | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Sets updatable. | ||
| 107 | * | ||
| 108 | * @return $this | ||
| 109 | */ | ||
| 110 | public function updatable(bool $flag = true): self | ||
| 111 | { | ||
| 112 | if (! $flag) { | ||
| 113 | $this->mapping['notUpdatable'] = true; | ||
| 114 | } | ||
| 115 | |||
| 116 | return $this; | ||
| 117 | } | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Sets scale. | ||
| 121 | * | ||
| 122 | * @return $this | ||
| 123 | */ | ||
| 124 | public function scale(int $s): static | ||
| 125 | { | ||
| 126 | $this->mapping['scale'] = $s; | ||
| 127 | |||
| 128 | return $this; | ||
| 129 | } | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Sets field as primary key. | ||
| 133 | * | ||
| 134 | * @return $this | ||
| 135 | */ | ||
| 136 | public function makePrimaryKey(): static | ||
| 137 | { | ||
| 138 | $this->mapping['id'] = true; | ||
| 139 | |||
| 140 | return $this; | ||
| 141 | } | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Sets an option. | ||
| 145 | * | ||
| 146 | * @return $this | ||
| 147 | */ | ||
| 148 | public function option(string $name, mixed $value): static | ||
| 149 | { | ||
| 150 | $this->mapping['options'][$name] = $value; | ||
| 151 | |||
| 152 | return $this; | ||
| 153 | } | ||
| 154 | |||
| 155 | /** @return $this */ | ||
| 156 | public function generatedValue(string $strategy = 'AUTO'): static | ||
| 157 | { | ||
| 158 | $this->generatedValue = $strategy; | ||
| 159 | |||
| 160 | return $this; | ||
| 161 | } | ||
| 162 | |||
| 163 | /** | ||
| 164 | * Sets field versioned. | ||
| 165 | * | ||
| 166 | * @return $this | ||
| 167 | */ | ||
| 168 | public function isVersionField(): static | ||
| 169 | { | ||
| 170 | $this->version = true; | ||
| 171 | |||
| 172 | return $this; | ||
| 173 | } | ||
| 174 | |||
| 175 | /** | ||
| 176 | * Sets Sequence Generator. | ||
| 177 | * | ||
| 178 | * @return $this | ||
| 179 | */ | ||
| 180 | public function setSequenceGenerator(string $sequenceName, int $allocationSize = 1, int $initialValue = 1): static | ||
| 181 | { | ||
| 182 | $this->sequenceDef = [ | ||
| 183 | 'sequenceName' => $sequenceName, | ||
| 184 | 'allocationSize' => $allocationSize, | ||
| 185 | 'initialValue' => $initialValue, | ||
| 186 | ]; | ||
| 187 | |||
| 188 | return $this; | ||
| 189 | } | ||
| 190 | |||
| 191 | /** | ||
| 192 | * Sets column definition. | ||
| 193 | * | ||
| 194 | * @return $this | ||
| 195 | */ | ||
| 196 | public function columnDefinition(string $def): static | ||
| 197 | { | ||
| 198 | $this->mapping['columnDefinition'] = $def; | ||
| 199 | |||
| 200 | return $this; | ||
| 201 | } | ||
| 202 | |||
| 203 | /** | ||
| 204 | * Set the FQCN of the custom ID generator. | ||
| 205 | * This class must extend \Doctrine\ORM\Id\AbstractIdGenerator. | ||
| 206 | * | ||
| 207 | * @return $this | ||
| 208 | */ | ||
| 209 | public function setCustomIdGenerator(string $customIdGenerator): static | ||
| 210 | { | ||
| 211 | $this->customIdGenerator = $customIdGenerator; | ||
| 212 | |||
| 213 | return $this; | ||
| 214 | } | ||
| 215 | |||
| 216 | /** | ||
| 217 | * Finalizes this field and attach it to the ClassMetadata. | ||
| 218 | * | ||
| 219 | * Without this call a FieldBuilder has no effect on the ClassMetadata. | ||
| 220 | */ | ||
| 221 | public function build(): ClassMetadataBuilder | ||
| 222 | { | ||
| 223 | $cm = $this->builder->getClassMetadata(); | ||
| 224 | if ($this->generatedValue) { | ||
| 225 | $cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue)); | ||
| 226 | } | ||
| 227 | |||
| 228 | if ($this->version) { | ||
| 229 | $cm->setVersionMapping($this->mapping); | ||
| 230 | } | ||
| 231 | |||
| 232 | $cm->mapField($this->mapping); | ||
| 233 | if ($this->sequenceDef) { | ||
| 234 | $cm->setSequenceGeneratorDefinition($this->sequenceDef); | ||
| 235 | } | ||
| 236 | |||
| 237 | if ($this->customIdGenerator) { | ||
| 238 | $cm->setCustomGeneratorDefinition(['class' => $this->customIdGenerator]); | ||
| 239 | } | ||
| 240 | |||
| 241 | return $this->builder; | ||
| 242 | } | ||
| 243 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * ManyToMany Association Builder | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.com | ||
| 11 | */ | ||
| 12 | class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder | ||
| 13 | { | ||
| 14 | private string|null $joinTableName = null; | ||
| 15 | |||
| 16 | /** @var mixed[] */ | ||
| 17 | private array $inverseJoinColumns = []; | ||
| 18 | |||
| 19 | /** @return $this */ | ||
| 20 | public function setJoinTable(string $name): static | ||
| 21 | { | ||
| 22 | $this->joinTableName = $name; | ||
| 23 | |||
| 24 | return $this; | ||
| 25 | } | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Adds Inverse Join Columns. | ||
| 29 | * | ||
| 30 | * @return $this | ||
| 31 | */ | ||
| 32 | public function addInverseJoinColumn( | ||
| 33 | string $columnName, | ||
| 34 | string $referencedColumnName, | ||
| 35 | bool $nullable = true, | ||
| 36 | bool $unique = false, | ||
| 37 | string|null $onDelete = null, | ||
| 38 | string|null $columnDef = null, | ||
| 39 | ): static { | ||
| 40 | $this->inverseJoinColumns[] = [ | ||
| 41 | 'name' => $columnName, | ||
| 42 | 'referencedColumnName' => $referencedColumnName, | ||
| 43 | 'nullable' => $nullable, | ||
| 44 | 'unique' => $unique, | ||
| 45 | 'onDelete' => $onDelete, | ||
| 46 | 'columnDefinition' => $columnDef, | ||
| 47 | ]; | ||
| 48 | |||
| 49 | return $this; | ||
| 50 | } | ||
| 51 | |||
| 52 | public function build(): ClassMetadataBuilder | ||
| 53 | { | ||
| 54 | $mapping = $this->mapping; | ||
| 55 | $mapping['joinTable'] = []; | ||
| 56 | if ($this->joinColumns) { | ||
| 57 | $mapping['joinTable']['joinColumns'] = $this->joinColumns; | ||
| 58 | } | ||
| 59 | |||
| 60 | if ($this->inverseJoinColumns) { | ||
| 61 | $mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns; | ||
| 62 | } | ||
| 63 | |||
| 64 | if ($this->joinTableName) { | ||
| 65 | $mapping['joinTable']['name'] = $this->joinTableName; | ||
| 66 | } | ||
| 67 | |||
| 68 | $cm = $this->builder->getClassMetadata(); | ||
| 69 | $cm->mapManyToMany($mapping); | ||
| 70 | |||
| 71 | return $this->builder; | ||
| 72 | } | ||
| 73 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Builder; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * OneToMany Association Builder | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.com | ||
| 11 | */ | ||
| 12 | class OneToManyAssociationBuilder extends AssociationBuilder | ||
| 13 | { | ||
| 14 | /** | ||
| 15 | * @psalm-param array<string, string> $fieldNames | ||
| 16 | * | ||
| 17 | * @return $this | ||
| 18 | */ | ||
| 19 | public function setOrderBy(array $fieldNames): static | ||
| 20 | { | ||
| 21 | $this->mapping['orderBy'] = $fieldNames; | ||
| 22 | |||
| 23 | return $this; | ||
| 24 | } | ||
| 25 | |||
| 26 | /** @return $this */ | ||
| 27 | public function setIndexBy(string $fieldName): static | ||
| 28 | { | ||
| 29 | $this->mapping['indexBy'] = $fieldName; | ||
| 30 | |||
| 31 | return $this; | ||
| 32 | } | ||
| 33 | |||
| 34 | public function build(): ClassMetadataBuilder | ||
| 35 | { | ||
| 36 | $mapping = $this->mapping; | ||
| 37 | if ($this->joinColumns) { | ||
| 38 | $mapping['joinColumns'] = $this->joinColumns; | ||
| 39 | } | ||
| 40 | |||
| 41 | $cm = $this->builder->getClassMetadata(); | ||
| 42 | $cm->mapOneToMany($mapping); | ||
| 43 | |||
| 44 | return $this->builder; | ||
| 45 | } | ||
| 46 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | /** Caching to an entity or a collection. */ | ||
| 10 | #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)] | ||
| 11 | final class Cache implements MappingAttribute | ||
| 12 | { | ||
| 13 | /** @psalm-param 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE' $usage */ | ||
| 14 | public function __construct( | ||
| 15 | public readonly string $usage = 'READ_ONLY', | ||
| 16 | public readonly string|null $region = null, | ||
| 17 | ) { | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ChainTypedFieldMapper.php b/vendor/doctrine/orm/src/Mapping/ChainTypedFieldMapper.php new file mode 100644 index 0000000..ed1ba93 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ChainTypedFieldMapper.php | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Internal\NoUnknownNamedArguments; | ||
| 8 | use ReflectionProperty; | ||
| 9 | |||
| 10 | final class ChainTypedFieldMapper implements TypedFieldMapper | ||
| 11 | { | ||
| 12 | use NoUnknownNamedArguments; | ||
| 13 | |||
| 14 | /** @var list<TypedFieldMapper> $typedFieldMappers */ | ||
| 15 | private readonly array $typedFieldMappers; | ||
| 16 | |||
| 17 | public function __construct(TypedFieldMapper ...$typedFieldMappers) | ||
| 18 | { | ||
| 19 | self::validateVariadicParameter($typedFieldMappers); | ||
| 20 | |||
| 21 | $this->typedFieldMappers = $typedFieldMappers; | ||
| 22 | } | ||
| 23 | |||
| 24 | /** | ||
| 25 | * {@inheritDoc} | ||
| 26 | */ | ||
| 27 | public function validateAndComplete(array $mapping, ReflectionProperty $field): array | ||
| 28 | { | ||
| 29 | foreach ($this->typedFieldMappers as $typedFieldMapper) { | ||
| 30 | $mapping = $typedFieldMapper->validateAndComplete($mapping, $field); | ||
| 31 | } | ||
| 32 | |||
| 33 | return $mapping; | ||
| 34 | } | ||
| 35 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 10 | final class ChangeTrackingPolicy implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** @psalm-param 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT' $value */ | ||
| 13 | public function __construct( | ||
| 14 | public readonly string $value, | ||
| 15 | ) { | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ClassMetadata.php b/vendor/doctrine/orm/src/Mapping/ClassMetadata.php new file mode 100644 index 0000000..f58e00e --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ClassMetadata.php | |||
| @@ -0,0 +1,2649 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use BadMethodCallException; | ||
| 9 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
| 10 | use Doctrine\Deprecations\Deprecation; | ||
| 11 | use Doctrine\Instantiator\Instantiator; | ||
| 12 | use Doctrine\Instantiator\InstantiatorInterface; | ||
| 13 | use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation; | ||
| 14 | use Doctrine\ORM\EntityRepository; | ||
| 15 | use Doctrine\ORM\Id\AbstractIdGenerator; | ||
| 16 | use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; | ||
| 17 | use Doctrine\Persistence\Mapping\ReflectionService; | ||
| 18 | use Doctrine\Persistence\Reflection\EnumReflectionProperty; | ||
| 19 | use InvalidArgumentException; | ||
| 20 | use LogicException; | ||
| 21 | use ReflectionClass; | ||
| 22 | use ReflectionNamedType; | ||
| 23 | use ReflectionProperty; | ||
| 24 | use Stringable; | ||
| 25 | |||
| 26 | use function array_diff; | ||
| 27 | use function array_intersect; | ||
| 28 | use function array_key_exists; | ||
| 29 | use function array_keys; | ||
| 30 | use function array_map; | ||
| 31 | use function array_merge; | ||
| 32 | use function array_pop; | ||
| 33 | use function array_values; | ||
| 34 | use function assert; | ||
| 35 | use function class_exists; | ||
| 36 | use function count; | ||
| 37 | use function enum_exists; | ||
| 38 | use function explode; | ||
| 39 | use function in_array; | ||
| 40 | use function interface_exists; | ||
| 41 | use function is_string; | ||
| 42 | use function is_subclass_of; | ||
| 43 | use function ltrim; | ||
| 44 | use function method_exists; | ||
| 45 | use function spl_object_id; | ||
| 46 | use function sprintf; | ||
| 47 | use function str_contains; | ||
| 48 | use function str_replace; | ||
| 49 | use function strtolower; | ||
| 50 | use function trait_exists; | ||
| 51 | use function trim; | ||
| 52 | |||
| 53 | /** | ||
| 54 | * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata | ||
| 55 | * of an entity and its associations. | ||
| 56 | * | ||
| 57 | * Once populated, ClassMetadata instances are usually cached in a serialized form. | ||
| 58 | * | ||
| 59 | * <b>IMPORTANT NOTE:</b> | ||
| 60 | * | ||
| 61 | * The fields of this class are only public for 2 reasons: | ||
| 62 | * 1) To allow fast READ access. | ||
| 63 | * 2) To drastically reduce the size of a serialized instance (private/protected members | ||
| 64 | * get the whole class name, namespace inclusive, prepended to every property in | ||
| 65 | * the serialized representation). | ||
| 66 | * | ||
| 67 | * @psalm-type ConcreteAssociationMapping = OneToOneOwningSideMapping|OneToOneInverseSideMapping|ManyToOneAssociationMapping|OneToManyAssociationMapping|ManyToManyOwningSideMapping|ManyToManyInverseSideMapping | ||
| 68 | * @template-covariant T of object | ||
| 69 | * @template-implements PersistenceClassMetadata<T> | ||
| 70 | */ | ||
| 71 | class ClassMetadata implements PersistenceClassMetadata, Stringable | ||
| 72 | { | ||
| 73 | /* The inheritance mapping types */ | ||
| 74 | /** | ||
| 75 | * NONE means the class does not participate in an inheritance hierarchy | ||
| 76 | * and therefore does not need an inheritance mapping type. | ||
| 77 | */ | ||
| 78 | public const INHERITANCE_TYPE_NONE = 1; | ||
| 79 | |||
| 80 | /** | ||
| 81 | * JOINED means the class will be persisted according to the rules of | ||
| 82 | * <tt>Class Table Inheritance</tt>. | ||
| 83 | */ | ||
| 84 | public const INHERITANCE_TYPE_JOINED = 2; | ||
| 85 | |||
| 86 | /** | ||
| 87 | * SINGLE_TABLE means the class will be persisted according to the rules of | ||
| 88 | * <tt>Single Table Inheritance</tt>. | ||
| 89 | */ | ||
| 90 | public const INHERITANCE_TYPE_SINGLE_TABLE = 3; | ||
| 91 | |||
| 92 | /* The Id generator types. */ | ||
| 93 | /** | ||
| 94 | * AUTO means the generator type will depend on what the used platform prefers. | ||
| 95 | * Offers full portability. | ||
| 96 | */ | ||
| 97 | public const GENERATOR_TYPE_AUTO = 1; | ||
| 98 | |||
| 99 | /** | ||
| 100 | * SEQUENCE means a separate sequence object will be used. Platforms that do | ||
| 101 | * not have native sequence support may emulate it. Full portability is currently | ||
| 102 | * not guaranteed. | ||
| 103 | */ | ||
| 104 | public const GENERATOR_TYPE_SEQUENCE = 2; | ||
| 105 | |||
| 106 | /** | ||
| 107 | * IDENTITY means an identity column is used for id generation. The database | ||
| 108 | * will fill in the id column on insertion. Platforms that do not support | ||
| 109 | * native identity columns may emulate them. Full portability is currently | ||
| 110 | * not guaranteed. | ||
| 111 | */ | ||
| 112 | public const GENERATOR_TYPE_IDENTITY = 4; | ||
| 113 | |||
| 114 | /** | ||
| 115 | * NONE means the class does not have a generated id. That means the class | ||
| 116 | * must have a natural, manually assigned id. | ||
| 117 | */ | ||
| 118 | public const GENERATOR_TYPE_NONE = 5; | ||
| 119 | |||
| 120 | /** | ||
| 121 | * CUSTOM means that customer will use own ID generator that supposedly work | ||
| 122 | */ | ||
| 123 | public const GENERATOR_TYPE_CUSTOM = 7; | ||
| 124 | |||
| 125 | /** | ||
| 126 | * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time | ||
| 127 | * by doing a property-by-property comparison with the original data. This will | ||
| 128 | * be done for all entities that are in MANAGED state at commit-time. | ||
| 129 | * | ||
| 130 | * This is the default change tracking policy. | ||
| 131 | */ | ||
| 132 | public const CHANGETRACKING_DEFERRED_IMPLICIT = 1; | ||
| 133 | |||
| 134 | /** | ||
| 135 | * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time | ||
| 136 | * by doing a property-by-property comparison with the original data. This will | ||
| 137 | * be done only for entities that were explicitly saved (through persist() or a cascade). | ||
| 138 | */ | ||
| 139 | public const CHANGETRACKING_DEFERRED_EXPLICIT = 2; | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Specifies that an association is to be fetched when it is first accessed. | ||
| 143 | */ | ||
| 144 | public const FETCH_LAZY = 2; | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Specifies that an association is to be fetched when the owner of the | ||
| 148 | * association is fetched. | ||
| 149 | */ | ||
| 150 | public const FETCH_EAGER = 3; | ||
| 151 | |||
| 152 | /** | ||
| 153 | * Specifies that an association is to be fetched lazy (on first access) and that | ||
| 154 | * commands such as Collection#count, Collection#slice are issued directly against | ||
| 155 | * the database if the collection is not yet initialized. | ||
| 156 | */ | ||
| 157 | public const FETCH_EXTRA_LAZY = 4; | ||
| 158 | |||
| 159 | /** | ||
| 160 | * Identifies a one-to-one association. | ||
| 161 | */ | ||
| 162 | public const ONE_TO_ONE = 1; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Identifies a many-to-one association. | ||
| 166 | */ | ||
| 167 | public const MANY_TO_ONE = 2; | ||
| 168 | |||
| 169 | /** | ||
| 170 | * Identifies a one-to-many association. | ||
| 171 | */ | ||
| 172 | public const ONE_TO_MANY = 4; | ||
| 173 | |||
| 174 | /** | ||
| 175 | * Identifies a many-to-many association. | ||
| 176 | */ | ||
| 177 | public const MANY_TO_MANY = 8; | ||
| 178 | |||
| 179 | /** | ||
| 180 | * Combined bitmask for to-one (single-valued) associations. | ||
| 181 | */ | ||
| 182 | public const TO_ONE = 3; | ||
| 183 | |||
| 184 | /** | ||
| 185 | * Combined bitmask for to-many (collection-valued) associations. | ||
| 186 | */ | ||
| 187 | public const TO_MANY = 12; | ||
| 188 | |||
| 189 | /** | ||
| 190 | * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks, | ||
| 191 | */ | ||
| 192 | public const CACHE_USAGE_READ_ONLY = 1; | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes. | ||
| 196 | */ | ||
| 197 | public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2; | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Read Write Attempts to lock the entity before update/delete. | ||
| 201 | */ | ||
| 202 | public const CACHE_USAGE_READ_WRITE = 3; | ||
| 203 | |||
| 204 | /** | ||
| 205 | * The value of this column is never generated by the database. | ||
| 206 | */ | ||
| 207 | public const GENERATED_NEVER = 0; | ||
| 208 | |||
| 209 | /** | ||
| 210 | * The value of this column is generated by the database on INSERT, but not on UPDATE. | ||
| 211 | */ | ||
| 212 | public const GENERATED_INSERT = 1; | ||
| 213 | |||
| 214 | /** | ||
| 215 | * The value of this column is generated by the database on both INSERT and UDPATE statements. | ||
| 216 | */ | ||
| 217 | public const GENERATED_ALWAYS = 2; | ||
| 218 | |||
| 219 | /** | ||
| 220 | * READ-ONLY: The namespace the entity class is contained in. | ||
| 221 | * | ||
| 222 | * @todo Not really needed. Usage could be localized. | ||
| 223 | */ | ||
| 224 | public string|null $namespace = null; | ||
| 225 | |||
| 226 | /** | ||
| 227 | * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance | ||
| 228 | * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same | ||
| 229 | * as {@link $name}. | ||
| 230 | * | ||
| 231 | * @psalm-var class-string | ||
| 232 | */ | ||
| 233 | public string $rootEntityName; | ||
| 234 | |||
| 235 | /** | ||
| 236 | * READ-ONLY: The definition of custom generator. Only used for CUSTOM | ||
| 237 | * generator type | ||
| 238 | * | ||
| 239 | * The definition has the following structure: | ||
| 240 | * <code> | ||
| 241 | * array( | ||
| 242 | * 'class' => 'ClassName', | ||
| 243 | * ) | ||
| 244 | * </code> | ||
| 245 | * | ||
| 246 | * @todo Merge with tableGeneratorDefinition into generic generatorDefinition | ||
| 247 | * @var array<string, string>|null | ||
| 248 | */ | ||
| 249 | public array|null $customGeneratorDefinition = null; | ||
| 250 | |||
| 251 | /** | ||
| 252 | * The name of the custom repository class used for the entity class. | ||
| 253 | * (Optional). | ||
| 254 | * | ||
| 255 | * @psalm-var ?class-string<EntityRepository> | ||
| 256 | */ | ||
| 257 | public string|null $customRepositoryClassName = null; | ||
| 258 | |||
| 259 | /** | ||
| 260 | * READ-ONLY: Whether this class describes the mapping of a mapped superclass. | ||
| 261 | */ | ||
| 262 | public bool $isMappedSuperclass = false; | ||
| 263 | |||
| 264 | /** | ||
| 265 | * READ-ONLY: Whether this class describes the mapping of an embeddable class. | ||
| 266 | */ | ||
| 267 | public bool $isEmbeddedClass = false; | ||
| 268 | |||
| 269 | /** | ||
| 270 | * READ-ONLY: The names of the parent <em>entity</em> classes (ancestors), starting with the | ||
| 271 | * nearest one and ending with the root entity class. | ||
| 272 | * | ||
| 273 | * @psalm-var list<class-string> | ||
| 274 | */ | ||
| 275 | public array $parentClasses = []; | ||
| 276 | |||
| 277 | /** | ||
| 278 | * READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all | ||
| 279 | * <em>entity</em> subclasses of this class. These may also be abstract classes. | ||
| 280 | * | ||
| 281 | * This list is used, for example, to enumerate all necessary tables in JTI when querying for root | ||
| 282 | * or subclass entities, or to gather all fields comprised in an entity inheritance tree. | ||
| 283 | * | ||
| 284 | * For classes that do not use STI/JTI, this list is empty. | ||
| 285 | * | ||
| 286 | * Implementation note: | ||
| 287 | * | ||
| 288 | * In PHP, there is no general way to discover all subclasses of a given class at runtime. For that | ||
| 289 | * reason, the list of classes given in the discriminator map at the root entity is considered | ||
| 290 | * authoritative. The discriminator map must contain all <em>concrete</em> classes that can | ||
| 291 | * appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract | ||
| 292 | * entity classes, users are not required to list such classes with a discriminator value. | ||
| 293 | * | ||
| 294 | * The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the | ||
| 295 | * root entity has been loaded. | ||
| 296 | * | ||
| 297 | * For subclasses of such root entities, the list can be reused/passed downwards, it only needs to | ||
| 298 | * be filtered accordingly (only keep remaining subclasses) | ||
| 299 | * | ||
| 300 | * @psalm-var list<class-string> | ||
| 301 | */ | ||
| 302 | public array $subClasses = []; | ||
| 303 | |||
| 304 | /** | ||
| 305 | * READ-ONLY: The names of all embedded classes based on properties. | ||
| 306 | * | ||
| 307 | * @psalm-var array<string, EmbeddedClassMapping> | ||
| 308 | */ | ||
| 309 | public array $embeddedClasses = []; | ||
| 310 | |||
| 311 | /** | ||
| 312 | * READ-ONLY: The field names of all fields that are part of the identifier/primary key | ||
| 313 | * of the mapped entity class. | ||
| 314 | * | ||
| 315 | * @psalm-var list<string> | ||
| 316 | */ | ||
| 317 | public array $identifier = []; | ||
| 318 | |||
| 319 | /** | ||
| 320 | * READ-ONLY: The inheritance mapping type used by the class. | ||
| 321 | * | ||
| 322 | * @psalm-var self::INHERITANCE_TYPE_* | ||
| 323 | */ | ||
| 324 | public int $inheritanceType = self::INHERITANCE_TYPE_NONE; | ||
| 325 | |||
| 326 | /** | ||
| 327 | * READ-ONLY: The Id generator type used by the class. | ||
| 328 | * | ||
| 329 | * @psalm-var self::GENERATOR_TYPE_* | ||
| 330 | */ | ||
| 331 | public int $generatorType = self::GENERATOR_TYPE_NONE; | ||
| 332 | |||
| 333 | /** | ||
| 334 | * READ-ONLY: The field mappings of the class. | ||
| 335 | * Keys are field names and values are FieldMapping instances | ||
| 336 | * | ||
| 337 | * @var array<string, FieldMapping> | ||
| 338 | */ | ||
| 339 | public array $fieldMappings = []; | ||
| 340 | |||
| 341 | /** | ||
| 342 | * READ-ONLY: An array of field names. Used to look up field names from column names. | ||
| 343 | * Keys are column names and values are field names. | ||
| 344 | * | ||
| 345 | * @psalm-var array<string, string> | ||
| 346 | */ | ||
| 347 | public array $fieldNames = []; | ||
| 348 | |||
| 349 | /** | ||
| 350 | * READ-ONLY: A map of field names to column names. Keys are field names and values column names. | ||
| 351 | * Used to look up column names from field names. | ||
| 352 | * This is the reverse lookup map of $_fieldNames. | ||
| 353 | * | ||
| 354 | * @deprecated 3.0 Remove this. | ||
| 355 | * | ||
| 356 | * @var mixed[] | ||
| 357 | */ | ||
| 358 | public array $columnNames = []; | ||
| 359 | |||
| 360 | /** | ||
| 361 | * READ-ONLY: The discriminator value of this class. | ||
| 362 | * | ||
| 363 | * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies | ||
| 364 | * where a discriminator column is used.</b> | ||
| 365 | * | ||
| 366 | * @see discriminatorColumn | ||
| 367 | */ | ||
| 368 | public mixed $discriminatorValue = null; | ||
| 369 | |||
| 370 | /** | ||
| 371 | * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. | ||
| 372 | * | ||
| 373 | * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies | ||
| 374 | * where a discriminator column is used.</b> | ||
| 375 | * | ||
| 376 | * @see discriminatorColumn | ||
| 377 | * | ||
| 378 | * @var array<int|string, string> | ||
| 379 | * | ||
| 380 | * @psalm-var array<int|string, class-string> | ||
| 381 | */ | ||
| 382 | public array $discriminatorMap = []; | ||
| 383 | |||
| 384 | /** | ||
| 385 | * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE | ||
| 386 | * inheritance mappings. | ||
| 387 | */ | ||
| 388 | public DiscriminatorColumnMapping|null $discriminatorColumn = null; | ||
| 389 | |||
| 390 | /** | ||
| 391 | * READ-ONLY: The primary table definition. The definition is an array with the | ||
| 392 | * following entries: | ||
| 393 | * | ||
| 394 | * name => <tableName> | ||
| 395 | * schema => <schemaName> | ||
| 396 | * indexes => array | ||
| 397 | * uniqueConstraints => array | ||
| 398 | * | ||
| 399 | * @var mixed[] | ||
| 400 | * @psalm-var array{ | ||
| 401 | * name: string, | ||
| 402 | * schema?: string, | ||
| 403 | * indexes?: array, | ||
| 404 | * uniqueConstraints?: array, | ||
| 405 | * options?: array<string, mixed>, | ||
| 406 | * quoted?: bool | ||
| 407 | * } | ||
| 408 | */ | ||
| 409 | public array $table; | ||
| 410 | |||
| 411 | /** | ||
| 412 | * READ-ONLY: The registered lifecycle callbacks for entities of this class. | ||
| 413 | * | ||
| 414 | * @psalm-var array<string, list<string>> | ||
| 415 | */ | ||
| 416 | public array $lifecycleCallbacks = []; | ||
| 417 | |||
| 418 | /** | ||
| 419 | * READ-ONLY: The registered entity listeners. | ||
| 420 | * | ||
| 421 | * @psalm-var array<string, list<array{class: class-string, method: string}>> | ||
| 422 | */ | ||
| 423 | public array $entityListeners = []; | ||
| 424 | |||
| 425 | /** | ||
| 426 | * READ-ONLY: The association mappings of this class. | ||
| 427 | * | ||
| 428 | * A join table definition has the following structure: | ||
| 429 | * <pre> | ||
| 430 | * array( | ||
| 431 | * 'name' => <join table name>, | ||
| 432 | * 'joinColumns' => array(<join column mapping from join table to source table>), | ||
| 433 | * 'inverseJoinColumns' => array(<join column mapping from join table to target table>) | ||
| 434 | * ) | ||
| 435 | * </pre> | ||
| 436 | * | ||
| 437 | * @psalm-var array<string, ConcreteAssociationMapping> | ||
| 438 | */ | ||
| 439 | public array $associationMappings = []; | ||
| 440 | |||
| 441 | /** | ||
| 442 | * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite. | ||
| 443 | */ | ||
| 444 | public bool $isIdentifierComposite = false; | ||
| 445 | |||
| 446 | /** | ||
| 447 | * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association. | ||
| 448 | * | ||
| 449 | * This flag is necessary because some code blocks require special treatment of this cases. | ||
| 450 | */ | ||
| 451 | public bool $containsForeignIdentifier = false; | ||
| 452 | |||
| 453 | /** | ||
| 454 | * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one ENUM type. | ||
| 455 | * | ||
| 456 | * This flag is necessary because some code blocks require special treatment of this cases. | ||
| 457 | */ | ||
| 458 | public bool $containsEnumIdentifier = false; | ||
| 459 | |||
| 460 | /** | ||
| 461 | * READ-ONLY: The ID generator used for generating IDs for this class. | ||
| 462 | * | ||
| 463 | * @todo Remove! | ||
| 464 | */ | ||
| 465 | public AbstractIdGenerator $idGenerator; | ||
| 466 | |||
| 467 | /** | ||
| 468 | * READ-ONLY: The definition of the sequence generator of this class. Only used for the | ||
| 469 | * SEQUENCE generation strategy. | ||
| 470 | * | ||
| 471 | * The definition has the following structure: | ||
| 472 | * <code> | ||
| 473 | * array( | ||
| 474 | * 'sequenceName' => 'name', | ||
| 475 | * 'allocationSize' => '20', | ||
| 476 | * 'initialValue' => '1' | ||
| 477 | * ) | ||
| 478 | * </code> | ||
| 479 | * | ||
| 480 | * @var array<string, mixed>|null | ||
| 481 | * @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}|null | ||
| 482 | * @todo Merge with tableGeneratorDefinition into generic generatorDefinition | ||
| 483 | */ | ||
| 484 | public array|null $sequenceGeneratorDefinition = null; | ||
| 485 | |||
| 486 | /** | ||
| 487 | * READ-ONLY: The policy used for change-tracking on entities of this class. | ||
| 488 | */ | ||
| 489 | public int $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; | ||
| 490 | |||
| 491 | /** | ||
| 492 | * READ-ONLY: A Flag indicating whether one or more columns of this class | ||
| 493 | * have to be reloaded after insert / update operations. | ||
| 494 | */ | ||
| 495 | public bool $requiresFetchAfterChange = false; | ||
| 496 | |||
| 497 | /** | ||
| 498 | * READ-ONLY: A flag for whether or not instances of this class are to be versioned | ||
| 499 | * with optimistic locking. | ||
| 500 | */ | ||
| 501 | public bool $isVersioned = false; | ||
| 502 | |||
| 503 | /** | ||
| 504 | * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). | ||
| 505 | */ | ||
| 506 | public string|null $versionField = null; | ||
| 507 | |||
| 508 | /** @var mixed[]|null */ | ||
| 509 | public array|null $cache = null; | ||
| 510 | |||
| 511 | /** | ||
| 512 | * The ReflectionClass instance of the mapped class. | ||
| 513 | * | ||
| 514 | * @var ReflectionClass<T>|null | ||
| 515 | */ | ||
| 516 | public ReflectionClass|null $reflClass = null; | ||
| 517 | |||
| 518 | /** | ||
| 519 | * Is this entity marked as "read-only"? | ||
| 520 | * | ||
| 521 | * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance | ||
| 522 | * optimization for entities that are immutable, either in your domain or through the relation database | ||
| 523 | * (coming from a view, or a history table for example). | ||
| 524 | */ | ||
| 525 | public bool $isReadOnly = false; | ||
| 526 | |||
| 527 | /** | ||
| 528 | * NamingStrategy determining the default column and table names. | ||
| 529 | */ | ||
| 530 | protected NamingStrategy $namingStrategy; | ||
| 531 | |||
| 532 | /** | ||
| 533 | * The ReflectionProperty instances of the mapped class. | ||
| 534 | * | ||
| 535 | * @var array<string, ReflectionProperty|null> | ||
| 536 | */ | ||
| 537 | public array $reflFields = []; | ||
| 538 | |||
| 539 | private InstantiatorInterface|null $instantiator = null; | ||
| 540 | |||
| 541 | private readonly TypedFieldMapper $typedFieldMapper; | ||
| 542 | |||
| 543 | /** | ||
| 544 | * Initializes a new ClassMetadata instance that will hold the object-relational mapping | ||
| 545 | * metadata of the class with the given name. | ||
| 546 | * | ||
| 547 | * @param string $name The name of the entity class the new instance is used for. | ||
| 548 | * @psalm-param class-string<T> $name | ||
| 549 | */ | ||
| 550 | public function __construct(public string $name, NamingStrategy|null $namingStrategy = null, TypedFieldMapper|null $typedFieldMapper = null) | ||
| 551 | { | ||
| 552 | $this->rootEntityName = $name; | ||
| 553 | $this->namingStrategy = $namingStrategy ?? new DefaultNamingStrategy(); | ||
| 554 | $this->instantiator = new Instantiator(); | ||
| 555 | $this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper(); | ||
| 556 | } | ||
| 557 | |||
| 558 | /** | ||
| 559 | * Gets the ReflectionProperties of the mapped class. | ||
| 560 | * | ||
| 561 | * @return ReflectionProperty[]|null[] An array of ReflectionProperty instances. | ||
| 562 | * @psalm-return array<ReflectionProperty|null> | ||
| 563 | */ | ||
| 564 | public function getReflectionProperties(): array | ||
| 565 | { | ||
| 566 | return $this->reflFields; | ||
| 567 | } | ||
| 568 | |||
| 569 | /** | ||
| 570 | * Gets a ReflectionProperty for a specific field of the mapped class. | ||
| 571 | */ | ||
| 572 | public function getReflectionProperty(string $name): ReflectionProperty|null | ||
| 573 | { | ||
| 574 | return $this->reflFields[$name]; | ||
| 575 | } | ||
| 576 | |||
| 577 | /** | ||
| 578 | * Gets the ReflectionProperty for the single identifier field. | ||
| 579 | * | ||
| 580 | * @throws BadMethodCallException If the class has a composite identifier. | ||
| 581 | */ | ||
| 582 | public function getSingleIdReflectionProperty(): ReflectionProperty|null | ||
| 583 | { | ||
| 584 | if ($this->isIdentifierComposite) { | ||
| 585 | throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.'); | ||
| 586 | } | ||
| 587 | |||
| 588 | return $this->reflFields[$this->identifier[0]]; | ||
| 589 | } | ||
| 590 | |||
| 591 | /** | ||
| 592 | * Extracts the identifier values of an entity of this class. | ||
| 593 | * | ||
| 594 | * For composite identifiers, the identifier values are returned as an array | ||
| 595 | * with the same order as the field order in {@link identifier}. | ||
| 596 | * | ||
| 597 | * @return array<string, mixed> | ||
| 598 | */ | ||
| 599 | public function getIdentifierValues(object $entity): array | ||
| 600 | { | ||
| 601 | if ($this->isIdentifierComposite) { | ||
| 602 | $id = []; | ||
| 603 | |||
| 604 | foreach ($this->identifier as $idField) { | ||
| 605 | $value = $this->reflFields[$idField]->getValue($entity); | ||
| 606 | |||
| 607 | if ($value !== null) { | ||
| 608 | $id[$idField] = $value; | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | return $id; | ||
| 613 | } | ||
| 614 | |||
| 615 | $id = $this->identifier[0]; | ||
| 616 | $value = $this->reflFields[$id]->getValue($entity); | ||
| 617 | |||
| 618 | if ($value === null) { | ||
| 619 | return []; | ||
| 620 | } | ||
| 621 | |||
| 622 | return [$id => $value]; | ||
| 623 | } | ||
| 624 | |||
| 625 | /** | ||
| 626 | * Populates the entity identifier of an entity. | ||
| 627 | * | ||
| 628 | * @psalm-param array<string, mixed> $id | ||
| 629 | * | ||
| 630 | * @todo Rename to assignIdentifier() | ||
| 631 | */ | ||
| 632 | public function setIdentifierValues(object $entity, array $id): void | ||
| 633 | { | ||
| 634 | foreach ($id as $idField => $idValue) { | ||
| 635 | $this->reflFields[$idField]->setValue($entity, $idValue); | ||
| 636 | } | ||
| 637 | } | ||
| 638 | |||
| 639 | /** | ||
| 640 | * Sets the specified field to the specified value on the given entity. | ||
| 641 | */ | ||
| 642 | public function setFieldValue(object $entity, string $field, mixed $value): void | ||
| 643 | { | ||
| 644 | $this->reflFields[$field]->setValue($entity, $value); | ||
| 645 | } | ||
| 646 | |||
| 647 | /** | ||
| 648 | * Gets the specified field's value off the given entity. | ||
| 649 | */ | ||
| 650 | public function getFieldValue(object $entity, string $field): mixed | ||
| 651 | { | ||
| 652 | return $this->reflFields[$field]->getValue($entity); | ||
| 653 | } | ||
| 654 | |||
| 655 | /** | ||
| 656 | * Creates a string representation of this instance. | ||
| 657 | * | ||
| 658 | * @return string The string representation of this instance. | ||
| 659 | * | ||
| 660 | * @todo Construct meaningful string representation. | ||
| 661 | */ | ||
| 662 | public function __toString(): string | ||
| 663 | { | ||
| 664 | return self::class . '@' . spl_object_id($this); | ||
| 665 | } | ||
| 666 | |||
| 667 | /** | ||
| 668 | * Determines which fields get serialized. | ||
| 669 | * | ||
| 670 | * It is only serialized what is necessary for best unserialization performance. | ||
| 671 | * That means any metadata properties that are not set or empty or simply have | ||
| 672 | * their default value are NOT serialized. | ||
| 673 | * | ||
| 674 | * Parts that are also NOT serialized because they can not be properly unserialized: | ||
| 675 | * - reflClass (ReflectionClass) | ||
| 676 | * - reflFields (ReflectionProperty array) | ||
| 677 | * | ||
| 678 | * @return string[] The names of all the fields that should be serialized. | ||
| 679 | */ | ||
| 680 | public function __sleep(): array | ||
| 681 | { | ||
| 682 | // This metadata is always serialized/cached. | ||
| 683 | $serialized = [ | ||
| 684 | 'associationMappings', | ||
| 685 | 'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName'] | ||
| 686 | 'fieldMappings', | ||
| 687 | 'fieldNames', | ||
| 688 | 'embeddedClasses', | ||
| 689 | 'identifier', | ||
| 690 | 'isIdentifierComposite', // TODO: REMOVE | ||
| 691 | 'name', | ||
| 692 | 'namespace', // TODO: REMOVE | ||
| 693 | 'table', | ||
| 694 | 'rootEntityName', | ||
| 695 | 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. | ||
| 696 | ]; | ||
| 697 | |||
| 698 | // The rest of the metadata is only serialized if necessary. | ||
| 699 | if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) { | ||
| 700 | $serialized[] = 'changeTrackingPolicy'; | ||
| 701 | } | ||
| 702 | |||
| 703 | if ($this->customRepositoryClassName) { | ||
| 704 | $serialized[] = 'customRepositoryClassName'; | ||
| 705 | } | ||
| 706 | |||
| 707 | if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE) { | ||
| 708 | $serialized[] = 'inheritanceType'; | ||
| 709 | $serialized[] = 'discriminatorColumn'; | ||
| 710 | $serialized[] = 'discriminatorValue'; | ||
| 711 | $serialized[] = 'discriminatorMap'; | ||
| 712 | $serialized[] = 'parentClasses'; | ||
| 713 | $serialized[] = 'subClasses'; | ||
| 714 | } | ||
| 715 | |||
| 716 | if ($this->generatorType !== self::GENERATOR_TYPE_NONE) { | ||
| 717 | $serialized[] = 'generatorType'; | ||
| 718 | if ($this->generatorType === self::GENERATOR_TYPE_SEQUENCE) { | ||
| 719 | $serialized[] = 'sequenceGeneratorDefinition'; | ||
| 720 | } | ||
| 721 | } | ||
| 722 | |||
| 723 | if ($this->isMappedSuperclass) { | ||
| 724 | $serialized[] = 'isMappedSuperclass'; | ||
| 725 | } | ||
| 726 | |||
| 727 | if ($this->isEmbeddedClass) { | ||
| 728 | $serialized[] = 'isEmbeddedClass'; | ||
| 729 | } | ||
| 730 | |||
| 731 | if ($this->containsForeignIdentifier) { | ||
| 732 | $serialized[] = 'containsForeignIdentifier'; | ||
| 733 | } | ||
| 734 | |||
| 735 | if ($this->containsEnumIdentifier) { | ||
| 736 | $serialized[] = 'containsEnumIdentifier'; | ||
| 737 | } | ||
| 738 | |||
| 739 | if ($this->isVersioned) { | ||
| 740 | $serialized[] = 'isVersioned'; | ||
| 741 | $serialized[] = 'versionField'; | ||
| 742 | } | ||
| 743 | |||
| 744 | if ($this->lifecycleCallbacks) { | ||
| 745 | $serialized[] = 'lifecycleCallbacks'; | ||
| 746 | } | ||
| 747 | |||
| 748 | if ($this->entityListeners) { | ||
| 749 | $serialized[] = 'entityListeners'; | ||
| 750 | } | ||
| 751 | |||
| 752 | if ($this->isReadOnly) { | ||
| 753 | $serialized[] = 'isReadOnly'; | ||
| 754 | } | ||
| 755 | |||
| 756 | if ($this->customGeneratorDefinition) { | ||
| 757 | $serialized[] = 'customGeneratorDefinition'; | ||
| 758 | } | ||
| 759 | |||
| 760 | if ($this->cache) { | ||
| 761 | $serialized[] = 'cache'; | ||
| 762 | } | ||
| 763 | |||
| 764 | if ($this->requiresFetchAfterChange) { | ||
| 765 | $serialized[] = 'requiresFetchAfterChange'; | ||
| 766 | } | ||
| 767 | |||
| 768 | return $serialized; | ||
| 769 | } | ||
| 770 | |||
| 771 | /** | ||
| 772 | * Creates a new instance of the mapped class, without invoking the constructor. | ||
| 773 | */ | ||
| 774 | public function newInstance(): object | ||
| 775 | { | ||
| 776 | return $this->instantiator->instantiate($this->name); | ||
| 777 | } | ||
| 778 | |||
| 779 | /** | ||
| 780 | * Restores some state that can not be serialized/unserialized. | ||
| 781 | */ | ||
| 782 | public function wakeupReflection(ReflectionService $reflService): void | ||
| 783 | { | ||
| 784 | // Restore ReflectionClass and properties | ||
| 785 | $this->reflClass = $reflService->getClass($this->name); | ||
| 786 | $this->instantiator = $this->instantiator ?: new Instantiator(); | ||
| 787 | |||
| 788 | $parentReflFields = []; | ||
| 789 | |||
| 790 | foreach ($this->embeddedClasses as $property => $embeddedClass) { | ||
| 791 | if (isset($embeddedClass->declaredField)) { | ||
| 792 | assert($embeddedClass->originalField !== null); | ||
| 793 | $childProperty = $this->getAccessibleProperty( | ||
| 794 | $reflService, | ||
| 795 | $this->embeddedClasses[$embeddedClass->declaredField]->class, | ||
| 796 | $embeddedClass->originalField, | ||
| 797 | ); | ||
| 798 | assert($childProperty !== null); | ||
| 799 | $parentReflFields[$property] = new ReflectionEmbeddedProperty( | ||
| 800 | $parentReflFields[$embeddedClass->declaredField], | ||
| 801 | $childProperty, | ||
| 802 | $this->embeddedClasses[$embeddedClass->declaredField]->class, | ||
| 803 | ); | ||
| 804 | |||
| 805 | continue; | ||
| 806 | } | ||
| 807 | |||
| 808 | $fieldRefl = $this->getAccessibleProperty( | ||
| 809 | $reflService, | ||
| 810 | $embeddedClass->declared ?? $this->name, | ||
| 811 | $property, | ||
| 812 | ); | ||
| 813 | |||
| 814 | $parentReflFields[$property] = $fieldRefl; | ||
| 815 | $this->reflFields[$property] = $fieldRefl; | ||
| 816 | } | ||
| 817 | |||
| 818 | foreach ($this->fieldMappings as $field => $mapping) { | ||
| 819 | if (isset($mapping->declaredField) && isset($parentReflFields[$mapping->declaredField])) { | ||
| 820 | assert($mapping->originalField !== null); | ||
| 821 | assert($mapping->originalClass !== null); | ||
| 822 | $childProperty = $this->getAccessibleProperty($reflService, $mapping->originalClass, $mapping->originalField); | ||
| 823 | assert($childProperty !== null); | ||
| 824 | |||
| 825 | if (isset($mapping->enumType)) { | ||
| 826 | $childProperty = new EnumReflectionProperty( | ||
| 827 | $childProperty, | ||
| 828 | $mapping->enumType, | ||
| 829 | ); | ||
| 830 | } | ||
| 831 | |||
| 832 | $this->reflFields[$field] = new ReflectionEmbeddedProperty( | ||
| 833 | $parentReflFields[$mapping->declaredField], | ||
| 834 | $childProperty, | ||
| 835 | $mapping->originalClass, | ||
| 836 | ); | ||
| 837 | continue; | ||
| 838 | } | ||
| 839 | |||
| 840 | $this->reflFields[$field] = isset($mapping->declared) | ||
| 841 | ? $this->getAccessibleProperty($reflService, $mapping->declared, $field) | ||
| 842 | : $this->getAccessibleProperty($reflService, $this->name, $field); | ||
| 843 | |||
| 844 | if (isset($mapping->enumType) && $this->reflFields[$field] !== null) { | ||
| 845 | $this->reflFields[$field] = new EnumReflectionProperty( | ||
| 846 | $this->reflFields[$field], | ||
| 847 | $mapping->enumType, | ||
| 848 | ); | ||
| 849 | } | ||
| 850 | } | ||
| 851 | |||
| 852 | foreach ($this->associationMappings as $field => $mapping) { | ||
| 853 | $this->reflFields[$field] = isset($mapping->declared) | ||
| 854 | ? $this->getAccessibleProperty($reflService, $mapping->declared, $field) | ||
| 855 | : $this->getAccessibleProperty($reflService, $this->name, $field); | ||
| 856 | } | ||
| 857 | } | ||
| 858 | |||
| 859 | /** | ||
| 860 | * Initializes a new ClassMetadata instance that will hold the object-relational mapping | ||
| 861 | * metadata of the class with the given name. | ||
| 862 | * | ||
| 863 | * @param ReflectionService $reflService The reflection service. | ||
| 864 | */ | ||
| 865 | public function initializeReflection(ReflectionService $reflService): void | ||
| 866 | { | ||
| 867 | $this->reflClass = $reflService->getClass($this->name); | ||
| 868 | $this->namespace = $reflService->getClassNamespace($this->name); | ||
| 869 | |||
| 870 | if ($this->reflClass) { | ||
| 871 | $this->name = $this->rootEntityName = $this->reflClass->name; | ||
| 872 | } | ||
| 873 | |||
| 874 | $this->table['name'] = $this->namingStrategy->classToTableName($this->name); | ||
| 875 | } | ||
| 876 | |||
| 877 | /** | ||
| 878 | * Validates Identifier. | ||
| 879 | * | ||
| 880 | * @throws MappingException | ||
| 881 | */ | ||
| 882 | public function validateIdentifier(): void | ||
| 883 | { | ||
| 884 | if ($this->isMappedSuperclass || $this->isEmbeddedClass) { | ||
| 885 | return; | ||
| 886 | } | ||
| 887 | |||
| 888 | // Verify & complete identifier mapping | ||
| 889 | if (! $this->identifier) { | ||
| 890 | throw MappingException::identifierRequired($this->name); | ||
| 891 | } | ||
| 892 | |||
| 893 | if ($this->usesIdGenerator() && $this->isIdentifierComposite) { | ||
| 894 | throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name); | ||
| 895 | } | ||
| 896 | } | ||
| 897 | |||
| 898 | /** | ||
| 899 | * Validates association targets actually exist. | ||
| 900 | * | ||
| 901 | * @throws MappingException | ||
| 902 | */ | ||
| 903 | public function validateAssociations(): void | ||
| 904 | { | ||
| 905 | foreach ($this->associationMappings as $mapping) { | ||
| 906 | if ( | ||
| 907 | ! class_exists($mapping->targetEntity) | ||
| 908 | && ! interface_exists($mapping->targetEntity) | ||
| 909 | && ! trait_exists($mapping->targetEntity) | ||
| 910 | ) { | ||
| 911 | throw MappingException::invalidTargetEntityClass($mapping->targetEntity, $this->name, $mapping->fieldName); | ||
| 912 | } | ||
| 913 | } | ||
| 914 | } | ||
| 915 | |||
| 916 | /** | ||
| 917 | * Validates lifecycle callbacks. | ||
| 918 | * | ||
| 919 | * @throws MappingException | ||
| 920 | */ | ||
| 921 | public function validateLifecycleCallbacks(ReflectionService $reflService): void | ||
| 922 | { | ||
| 923 | foreach ($this->lifecycleCallbacks as $callbacks) { | ||
| 924 | foreach ($callbacks as $callbackFuncName) { | ||
| 925 | if (! $reflService->hasPublicMethod($this->name, $callbackFuncName)) { | ||
| 926 | throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName); | ||
| 927 | } | ||
| 928 | } | ||
| 929 | } | ||
| 930 | } | ||
| 931 | |||
| 932 | /** | ||
| 933 | * {@inheritDoc} | ||
| 934 | * | ||
| 935 | * Can return null when using static reflection, in violation of the LSP | ||
| 936 | */ | ||
| 937 | public function getReflectionClass(): ReflectionClass|null | ||
| 938 | { | ||
| 939 | return $this->reflClass; | ||
| 940 | } | ||
| 941 | |||
| 942 | /** @psalm-param array{usage?: mixed, region?: mixed} $cache */ | ||
| 943 | public function enableCache(array $cache): void | ||
| 944 | { | ||
| 945 | if (! isset($cache['usage'])) { | ||
| 946 | $cache['usage'] = self::CACHE_USAGE_READ_ONLY; | ||
| 947 | } | ||
| 948 | |||
| 949 | if (! isset($cache['region'])) { | ||
| 950 | $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)); | ||
| 951 | } | ||
| 952 | |||
| 953 | $this->cache = $cache; | ||
| 954 | } | ||
| 955 | |||
| 956 | /** @psalm-param array{usage?: int, region?: string} $cache */ | ||
| 957 | public function enableAssociationCache(string $fieldName, array $cache): void | ||
| 958 | { | ||
| 959 | $this->associationMappings[$fieldName]->cache = $this->getAssociationCacheDefaults($fieldName, $cache); | ||
| 960 | } | ||
| 961 | |||
| 962 | /** | ||
| 963 | * @psalm-param array{usage?: int, region?: string|null} $cache | ||
| 964 | * | ||
| 965 | * @return int[]|string[] | ||
| 966 | * @psalm-return array{usage: int, region: string|null} | ||
| 967 | */ | ||
| 968 | public function getAssociationCacheDefaults(string $fieldName, array $cache): array | ||
| 969 | { | ||
| 970 | if (! isset($cache['usage'])) { | ||
| 971 | $cache['usage'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY; | ||
| 972 | } | ||
| 973 | |||
| 974 | if (! isset($cache['region'])) { | ||
| 975 | $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName; | ||
| 976 | } | ||
| 977 | |||
| 978 | return $cache; | ||
| 979 | } | ||
| 980 | |||
| 981 | /** | ||
| 982 | * Sets the change tracking policy used by this class. | ||
| 983 | */ | ||
| 984 | public function setChangeTrackingPolicy(int $policy): void | ||
| 985 | { | ||
| 986 | $this->changeTrackingPolicy = $policy; | ||
| 987 | } | ||
| 988 | |||
| 989 | /** | ||
| 990 | * Whether the change tracking policy of this class is "deferred explicit". | ||
| 991 | */ | ||
| 992 | public function isChangeTrackingDeferredExplicit(): bool | ||
| 993 | { | ||
| 994 | return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT; | ||
| 995 | } | ||
| 996 | |||
| 997 | /** | ||
| 998 | * Whether the change tracking policy of this class is "deferred implicit". | ||
| 999 | */ | ||
| 1000 | public function isChangeTrackingDeferredImplicit(): bool | ||
| 1001 | { | ||
| 1002 | return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT; | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | /** | ||
| 1006 | * Checks whether a field is part of the identifier/primary key field(s). | ||
| 1007 | */ | ||
| 1008 | public function isIdentifier(string $fieldName): bool | ||
| 1009 | { | ||
| 1010 | if (! $this->identifier) { | ||
| 1011 | return false; | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | if (! $this->isIdentifierComposite) { | ||
| 1015 | return $fieldName === $this->identifier[0]; | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | return in_array($fieldName, $this->identifier, true); | ||
| 1019 | } | ||
| 1020 | |||
| 1021 | public function isUniqueField(string $fieldName): bool | ||
| 1022 | { | ||
| 1023 | $mapping = $this->getFieldMapping($fieldName); | ||
| 1024 | |||
| 1025 | return $mapping !== false && isset($mapping->unique) && $mapping->unique; | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | public function isNullable(string $fieldName): bool | ||
| 1029 | { | ||
| 1030 | $mapping = $this->getFieldMapping($fieldName); | ||
| 1031 | |||
| 1032 | return $mapping !== false && isset($mapping->nullable) && $mapping->nullable; | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | /** | ||
| 1036 | * Gets a column name for a field name. | ||
| 1037 | * If the column name for the field cannot be found, the given field name | ||
| 1038 | * is returned. | ||
| 1039 | */ | ||
| 1040 | public function getColumnName(string $fieldName): string | ||
| 1041 | { | ||
| 1042 | return $this->columnNames[$fieldName] ?? $fieldName; | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | /** | ||
| 1046 | * Gets the mapping of a (regular) field that holds some data but not a | ||
| 1047 | * reference to another object. | ||
| 1048 | * | ||
| 1049 | * @throws MappingException | ||
| 1050 | */ | ||
| 1051 | public function getFieldMapping(string $fieldName): FieldMapping | ||
| 1052 | { | ||
| 1053 | if (! isset($this->fieldMappings[$fieldName])) { | ||
| 1054 | throw MappingException::mappingNotFound($this->name, $fieldName); | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | return $this->fieldMappings[$fieldName]; | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | /** | ||
| 1061 | * Gets the mapping of an association. | ||
| 1062 | * | ||
| 1063 | * @see ClassMetadata::$associationMappings | ||
| 1064 | * | ||
| 1065 | * @param string $fieldName The field name that represents the association in | ||
| 1066 | * the object model. | ||
| 1067 | * | ||
| 1068 | * @throws MappingException | ||
| 1069 | */ | ||
| 1070 | public function getAssociationMapping(string $fieldName): AssociationMapping | ||
| 1071 | { | ||
| 1072 | if (! isset($this->associationMappings[$fieldName])) { | ||
| 1073 | throw MappingException::mappingNotFound($this->name, $fieldName); | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | return $this->associationMappings[$fieldName]; | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | /** | ||
| 1080 | * Gets all association mappings of the class. | ||
| 1081 | * | ||
| 1082 | * @psalm-return array<string, AssociationMapping> | ||
| 1083 | */ | ||
| 1084 | public function getAssociationMappings(): array | ||
| 1085 | { | ||
| 1086 | return $this->associationMappings; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | /** | ||
| 1090 | * Gets the field name for a column name. | ||
| 1091 | * If no field name can be found the column name is returned. | ||
| 1092 | * | ||
| 1093 | * @return string The column alias. | ||
| 1094 | */ | ||
| 1095 | public function getFieldName(string $columnName): string | ||
| 1096 | { | ||
| 1097 | return $this->fieldNames[$columnName] ?? $columnName; | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | /** | ||
| 1101 | * Checks whether given property has type | ||
| 1102 | */ | ||
| 1103 | private function isTypedProperty(string $name): bool | ||
| 1104 | { | ||
| 1105 | return isset($this->reflClass) | ||
| 1106 | && $this->reflClass->hasProperty($name) | ||
| 1107 | && $this->reflClass->getProperty($name)->hasType(); | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | /** | ||
| 1111 | * Validates & completes the given field mapping based on typed property. | ||
| 1112 | * | ||
| 1113 | * @param array{fieldName: string, type?: string} $mapping The field mapping to validate & complete. | ||
| 1114 | * | ||
| 1115 | * @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping. | ||
| 1116 | */ | ||
| 1117 | private function validateAndCompleteTypedFieldMapping(array $mapping): array | ||
| 1118 | { | ||
| 1119 | $field = $this->reflClass->getProperty($mapping['fieldName']); | ||
| 1120 | |||
| 1121 | $mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field); | ||
| 1122 | |||
| 1123 | return $mapping; | ||
| 1124 | } | ||
| 1125 | |||
| 1126 | /** | ||
| 1127 | * Validates & completes the basic mapping information based on typed property. | ||
| 1128 | * | ||
| 1129 | * @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. | ||
| 1130 | * | ||
| 1131 | * @return mixed[] The updated mapping. | ||
| 1132 | */ | ||
| 1133 | private function validateAndCompleteTypedAssociationMapping(array $mapping): array | ||
| 1134 | { | ||
| 1135 | $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); | ||
| 1136 | |||
| 1137 | if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) { | ||
| 1138 | return $mapping; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) { | ||
| 1142 | $mapping['targetEntity'] = $type->getName(); | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | return $mapping; | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | /** | ||
| 1149 | * Validates & completes the given field mapping. | ||
| 1150 | * | ||
| 1151 | * @psalm-param array{ | ||
| 1152 | * fieldName?: string, | ||
| 1153 | * columnName?: string, | ||
| 1154 | * id?: bool, | ||
| 1155 | * generated?: self::GENERATED_*, | ||
| 1156 | * enumType?: class-string, | ||
| 1157 | * } $mapping The field mapping to validate & complete. | ||
| 1158 | * | ||
| 1159 | * @return FieldMapping The updated mapping. | ||
| 1160 | * | ||
| 1161 | * @throws MappingException | ||
| 1162 | */ | ||
| 1163 | protected function validateAndCompleteFieldMapping(array $mapping): FieldMapping | ||
| 1164 | { | ||
| 1165 | // Check mandatory fields | ||
| 1166 | if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { | ||
| 1167 | throw MappingException::missingFieldName($this->name); | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | if ($this->isTypedProperty($mapping['fieldName'])) { | ||
| 1171 | $mapping = $this->validateAndCompleteTypedFieldMapping($mapping); | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | if (! isset($mapping['type'])) { | ||
| 1175 | // Default to string | ||
| 1176 | $mapping['type'] = 'string'; | ||
| 1177 | } | ||
| 1178 | |||
| 1179 | // Complete fieldName and columnName mapping | ||
| 1180 | if (! isset($mapping['columnName'])) { | ||
| 1181 | $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name); | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | $mapping = FieldMapping::fromMappingArray($mapping); | ||
| 1185 | |||
| 1186 | if ($mapping->columnName[0] === '`') { | ||
| 1187 | $mapping->columnName = trim($mapping->columnName, '`'); | ||
| 1188 | $mapping->quoted = true; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | $this->columnNames[$mapping->fieldName] = $mapping->columnName; | ||
| 1192 | |||
| 1193 | if (isset($this->fieldNames[$mapping->columnName]) || ($this->discriminatorColumn && $this->discriminatorColumn->name === $mapping->columnName)) { | ||
| 1194 | throw MappingException::duplicateColumnName($this->name, $mapping->columnName); | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | $this->fieldNames[$mapping->columnName] = $mapping->fieldName; | ||
| 1198 | |||
| 1199 | // Complete id mapping | ||
| 1200 | if (isset($mapping->id) && $mapping->id === true) { | ||
| 1201 | if ($this->versionField === $mapping->fieldName) { | ||
| 1202 | throw MappingException::cannotVersionIdField($this->name, $mapping->fieldName); | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | if (! in_array($mapping->fieldName, $this->identifier, true)) { | ||
| 1206 | $this->identifier[] = $mapping->fieldName; | ||
| 1207 | } | ||
| 1208 | |||
| 1209 | // Check for composite key | ||
| 1210 | if (! $this->isIdentifierComposite && count($this->identifier) > 1) { | ||
| 1211 | $this->isIdentifierComposite = true; | ||
| 1212 | } | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | if (isset($mapping->generated)) { | ||
| 1216 | if (! in_array($mapping->generated, [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) { | ||
| 1217 | throw MappingException::invalidGeneratedMode($mapping->generated); | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | if ($mapping->generated === self::GENERATED_NEVER) { | ||
| 1221 | unset($mapping->generated); | ||
| 1222 | } | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | if (isset($mapping->enumType)) { | ||
| 1226 | if (! enum_exists($mapping->enumType)) { | ||
| 1227 | throw MappingException::nonEnumTypeMapped($this->name, $mapping->fieldName, $mapping->enumType); | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | if (! empty($mapping->id)) { | ||
| 1231 | $this->containsEnumIdentifier = true; | ||
| 1232 | } | ||
| 1233 | } | ||
| 1234 | |||
| 1235 | return $mapping; | ||
| 1236 | } | ||
| 1237 | |||
| 1238 | /** | ||
| 1239 | * Validates & completes the basic mapping information that is common to all | ||
| 1240 | * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). | ||
| 1241 | * | ||
| 1242 | * @psalm-param array<string, mixed> $mapping The mapping. | ||
| 1243 | * | ||
| 1244 | * @return ConcreteAssociationMapping | ||
| 1245 | * | ||
| 1246 | * @throws MappingException If something is wrong with the mapping. | ||
| 1247 | */ | ||
| 1248 | protected function _validateAndCompleteAssociationMapping(array $mapping): AssociationMapping | ||
| 1249 | { | ||
| 1250 | if (array_key_exists('mappedBy', $mapping) && $mapping['mappedBy'] === null) { | ||
| 1251 | unset($mapping['mappedBy']); | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | if (array_key_exists('inversedBy', $mapping) && $mapping['inversedBy'] === null) { | ||
| 1255 | unset($mapping['inversedBy']); | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | if (array_key_exists('joinColumns', $mapping) && in_array($mapping['joinColumns'], [null, []], true)) { | ||
| 1259 | unset($mapping['joinColumns']); | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy | ||
| 1263 | |||
| 1264 | if (empty($mapping['indexBy'])) { | ||
| 1265 | unset($mapping['indexBy']); | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | // If targetEntity is unqualified, assume it is in the same namespace as | ||
| 1269 | // the sourceEntity. | ||
| 1270 | $mapping['sourceEntity'] = $this->name; | ||
| 1271 | |||
| 1272 | if ($this->isTypedProperty($mapping['fieldName'])) { | ||
| 1273 | $mapping = $this->validateAndCompleteTypedAssociationMapping($mapping); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | if (isset($mapping['targetEntity'])) { | ||
| 1277 | $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']); | ||
| 1278 | $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { | ||
| 1282 | throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']); | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | // Complete id mapping | ||
| 1286 | if (isset($mapping['id']) && $mapping['id'] === true) { | ||
| 1287 | if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { | ||
| 1288 | throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | if (! in_array($mapping['fieldName'], $this->identifier, true)) { | ||
| 1292 | if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) { | ||
| 1293 | throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( | ||
| 1294 | $mapping['targetEntity'], | ||
| 1295 | $this->name, | ||
| 1296 | $mapping['fieldName'], | ||
| 1297 | ); | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | assert(is_string($mapping['fieldName'])); | ||
| 1301 | $this->identifier[] = $mapping['fieldName']; | ||
| 1302 | $this->containsForeignIdentifier = true; | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | // Check for composite key | ||
| 1306 | if (! $this->isIdentifierComposite && count($this->identifier) > 1) { | ||
| 1307 | $this->isIdentifierComposite = true; | ||
| 1308 | } | ||
| 1309 | |||
| 1310 | if ($this->cache && ! isset($mapping['cache'])) { | ||
| 1311 | throw NonCacheableEntityAssociation::fromEntityAndField( | ||
| 1312 | $this->name, | ||
| 1313 | $mapping['fieldName'], | ||
| 1314 | ); | ||
| 1315 | } | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | // Mandatory attributes for both sides | ||
| 1319 | // Mandatory: fieldName, targetEntity | ||
| 1320 | if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { | ||
| 1321 | throw MappingException::missingFieldName($this->name); | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | if (! isset($mapping['targetEntity'])) { | ||
| 1325 | throw MappingException::missingTargetEntity($mapping['fieldName']); | ||
| 1326 | } | ||
| 1327 | |||
| 1328 | // Mandatory and optional attributes for either side | ||
| 1329 | if (! isset($mapping['mappedBy'])) { | ||
| 1330 | if (isset($mapping['joinTable'])) { | ||
| 1331 | if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') { | ||
| 1332 | $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`'); | ||
| 1333 | $mapping['joinTable']['quoted'] = true; | ||
| 1334 | } | ||
| 1335 | } | ||
| 1336 | } else { | ||
| 1337 | $mapping['isOwningSide'] = false; | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { | ||
| 1341 | throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']); | ||
| 1342 | } | ||
| 1343 | |||
| 1344 | // Fetch mode. Default fetch mode to LAZY, if not set. | ||
| 1345 | if (! isset($mapping['fetch'])) { | ||
| 1346 | $mapping['fetch'] = self::FETCH_LAZY; | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | // Cascades | ||
| 1350 | $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : []; | ||
| 1351 | |||
| 1352 | $allCascades = ['remove', 'persist', 'refresh', 'detach']; | ||
| 1353 | if (in_array('all', $cascades, true)) { | ||
| 1354 | $cascades = $allCascades; | ||
| 1355 | } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) { | ||
| 1356 | throw MappingException::invalidCascadeOption( | ||
| 1357 | array_diff($cascades, $allCascades), | ||
| 1358 | $this->name, | ||
| 1359 | $mapping['fieldName'], | ||
| 1360 | ); | ||
| 1361 | } | ||
| 1362 | |||
| 1363 | $mapping['cascade'] = $cascades; | ||
| 1364 | |||
| 1365 | switch ($mapping['type']) { | ||
| 1366 | case self::ONE_TO_ONE: | ||
| 1367 | if (isset($mapping['joinColumns']) && $mapping['joinColumns'] && ! $mapping['isOwningSide']) { | ||
| 1368 | throw MappingException::joinColumnNotAllowedOnOneToOneInverseSide( | ||
| 1369 | $this->name, | ||
| 1370 | $mapping['fieldName'], | ||
| 1371 | ); | ||
| 1372 | } | ||
| 1373 | |||
| 1374 | return $mapping['isOwningSide'] ? | ||
| 1375 | OneToOneOwningSideMapping::fromMappingArrayAndName( | ||
| 1376 | $mapping, | ||
| 1377 | $this->namingStrategy, | ||
| 1378 | $this->name, | ||
| 1379 | $this->table ?? null, | ||
| 1380 | $this->isInheritanceTypeSingleTable(), | ||
| 1381 | ) : | ||
| 1382 | OneToOneInverseSideMapping::fromMappingArrayAndName($mapping, $this->name); | ||
| 1383 | |||
| 1384 | case self::MANY_TO_ONE: | ||
| 1385 | return ManyToOneAssociationMapping::fromMappingArrayAndName( | ||
| 1386 | $mapping, | ||
| 1387 | $this->namingStrategy, | ||
| 1388 | $this->name, | ||
| 1389 | $this->table ?? null, | ||
| 1390 | $this->isInheritanceTypeSingleTable(), | ||
| 1391 | ); | ||
| 1392 | |||
| 1393 | case self::ONE_TO_MANY: | ||
| 1394 | return OneToManyAssociationMapping::fromMappingArrayAndName($mapping, $this->name); | ||
| 1395 | |||
| 1396 | case self::MANY_TO_MANY: | ||
| 1397 | if (isset($mapping['joinColumns'])) { | ||
| 1398 | unset($mapping['joinColumns']); | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | return $mapping['isOwningSide'] ? | ||
| 1402 | ManyToManyOwningSideMapping::fromMappingArrayAndNamingStrategy($mapping, $this->namingStrategy) : | ||
| 1403 | ManyToManyInverseSideMapping::fromMappingArray($mapping); | ||
| 1404 | |||
| 1405 | default: | ||
| 1406 | throw MappingException::invalidAssociationType( | ||
| 1407 | $this->name, | ||
| 1408 | $mapping['fieldName'], | ||
| 1409 | $mapping['type'], | ||
| 1410 | ); | ||
| 1411 | } | ||
| 1412 | } | ||
| 1413 | |||
| 1414 | /** | ||
| 1415 | * {@inheritDoc} | ||
| 1416 | */ | ||
| 1417 | public function getIdentifierFieldNames(): array | ||
| 1418 | { | ||
| 1419 | return $this->identifier; | ||
| 1420 | } | ||
| 1421 | |||
| 1422 | /** | ||
| 1423 | * Gets the name of the single id field. Note that this only works on | ||
| 1424 | * entity classes that have a single-field pk. | ||
| 1425 | * | ||
| 1426 | * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. | ||
| 1427 | */ | ||
| 1428 | public function getSingleIdentifierFieldName(): string | ||
| 1429 | { | ||
| 1430 | if ($this->isIdentifierComposite) { | ||
| 1431 | throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name); | ||
| 1432 | } | ||
| 1433 | |||
| 1434 | if (! isset($this->identifier[0])) { | ||
| 1435 | throw MappingException::noIdDefined($this->name); | ||
| 1436 | } | ||
| 1437 | |||
| 1438 | return $this->identifier[0]; | ||
| 1439 | } | ||
| 1440 | |||
| 1441 | /** | ||
| 1442 | * Gets the column name of the single id column. Note that this only works on | ||
| 1443 | * entity classes that have a single-field pk. | ||
| 1444 | * | ||
| 1445 | * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. | ||
| 1446 | */ | ||
| 1447 | public function getSingleIdentifierColumnName(): string | ||
| 1448 | { | ||
| 1449 | return $this->getColumnName($this->getSingleIdentifierFieldName()); | ||
| 1450 | } | ||
| 1451 | |||
| 1452 | /** | ||
| 1453 | * INTERNAL: | ||
| 1454 | * Sets the mapped identifier/primary key fields of this class. | ||
| 1455 | * Mainly used by the ClassMetadataFactory to assign inherited identifiers. | ||
| 1456 | * | ||
| 1457 | * @psalm-param list<mixed> $identifier | ||
| 1458 | */ | ||
| 1459 | public function setIdentifier(array $identifier): void | ||
| 1460 | { | ||
| 1461 | $this->identifier = $identifier; | ||
| 1462 | $this->isIdentifierComposite = (count($this->identifier) > 1); | ||
| 1463 | } | ||
| 1464 | |||
| 1465 | /** | ||
| 1466 | * {@inheritDoc} | ||
| 1467 | */ | ||
| 1468 | public function getIdentifier(): array | ||
| 1469 | { | ||
| 1470 | return $this->identifier; | ||
| 1471 | } | ||
| 1472 | |||
| 1473 | public function hasField(string $fieldName): bool | ||
| 1474 | { | ||
| 1475 | return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]); | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | /** | ||
| 1479 | * Gets an array containing all the column names. | ||
| 1480 | * | ||
| 1481 | * @psalm-param list<string>|null $fieldNames | ||
| 1482 | * | ||
| 1483 | * @return mixed[] | ||
| 1484 | * @psalm-return list<string> | ||
| 1485 | */ | ||
| 1486 | public function getColumnNames(array|null $fieldNames = null): array | ||
| 1487 | { | ||
| 1488 | if ($fieldNames === null) { | ||
| 1489 | return array_keys($this->fieldNames); | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | return array_values(array_map($this->getColumnName(...), $fieldNames)); | ||
| 1493 | } | ||
| 1494 | |||
| 1495 | /** | ||
| 1496 | * Returns an array with all the identifier column names. | ||
| 1497 | * | ||
| 1498 | * @psalm-return list<string> | ||
| 1499 | */ | ||
| 1500 | public function getIdentifierColumnNames(): array | ||
| 1501 | { | ||
| 1502 | $columnNames = []; | ||
| 1503 | |||
| 1504 | foreach ($this->identifier as $idProperty) { | ||
| 1505 | if (isset($this->fieldMappings[$idProperty])) { | ||
| 1506 | $columnNames[] = $this->fieldMappings[$idProperty]->columnName; | ||
| 1507 | |||
| 1508 | continue; | ||
| 1509 | } | ||
| 1510 | |||
| 1511 | // Association defined as Id field | ||
| 1512 | assert($this->associationMappings[$idProperty]->isToOneOwningSide()); | ||
| 1513 | $joinColumns = $this->associationMappings[$idProperty]->joinColumns; | ||
| 1514 | $assocColumnNames = array_map(static fn (JoinColumnMapping $joinColumn): string => $joinColumn->name, $joinColumns); | ||
| 1515 | |||
| 1516 | $columnNames = array_merge($columnNames, $assocColumnNames); | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | return $columnNames; | ||
| 1520 | } | ||
| 1521 | |||
| 1522 | /** | ||
| 1523 | * Sets the type of Id generator to use for the mapped class. | ||
| 1524 | * | ||
| 1525 | * @psalm-param self::GENERATOR_TYPE_* $generatorType | ||
| 1526 | */ | ||
| 1527 | public function setIdGeneratorType(int $generatorType): void | ||
| 1528 | { | ||
| 1529 | $this->generatorType = $generatorType; | ||
| 1530 | } | ||
| 1531 | |||
| 1532 | /** | ||
| 1533 | * Checks whether the mapped class uses an Id generator. | ||
| 1534 | */ | ||
| 1535 | public function usesIdGenerator(): bool | ||
| 1536 | { | ||
| 1537 | return $this->generatorType !== self::GENERATOR_TYPE_NONE; | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | public function isInheritanceTypeNone(): bool | ||
| 1541 | { | ||
| 1542 | return $this->inheritanceType === self::INHERITANCE_TYPE_NONE; | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | /** | ||
| 1546 | * Checks whether the mapped class uses the JOINED inheritance mapping strategy. | ||
| 1547 | * | ||
| 1548 | * @return bool TRUE if the class participates in a JOINED inheritance mapping, | ||
| 1549 | * FALSE otherwise. | ||
| 1550 | */ | ||
| 1551 | public function isInheritanceTypeJoined(): bool | ||
| 1552 | { | ||
| 1553 | return $this->inheritanceType === self::INHERITANCE_TYPE_JOINED; | ||
| 1554 | } | ||
| 1555 | |||
| 1556 | /** | ||
| 1557 | * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. | ||
| 1558 | * | ||
| 1559 | * @return bool TRUE if the class participates in a SINGLE_TABLE inheritance mapping, | ||
| 1560 | * FALSE otherwise. | ||
| 1561 | */ | ||
| 1562 | public function isInheritanceTypeSingleTable(): bool | ||
| 1563 | { | ||
| 1564 | return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_TABLE; | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | /** | ||
| 1568 | * Checks whether the class uses an identity column for the Id generation. | ||
| 1569 | */ | ||
| 1570 | public function isIdGeneratorIdentity(): bool | ||
| 1571 | { | ||
| 1572 | return $this->generatorType === self::GENERATOR_TYPE_IDENTITY; | ||
| 1573 | } | ||
| 1574 | |||
| 1575 | /** | ||
| 1576 | * Checks whether the class uses a sequence for id generation. | ||
| 1577 | * | ||
| 1578 | * @psalm-assert-if-true !null $this->sequenceGeneratorDefinition | ||
| 1579 | */ | ||
| 1580 | public function isIdGeneratorSequence(): bool | ||
| 1581 | { | ||
| 1582 | return $this->generatorType === self::GENERATOR_TYPE_SEQUENCE; | ||
| 1583 | } | ||
| 1584 | |||
| 1585 | /** | ||
| 1586 | * Checks whether the class has a natural identifier/pk (which means it does | ||
| 1587 | * not use any Id generator. | ||
| 1588 | */ | ||
| 1589 | public function isIdentifierNatural(): bool | ||
| 1590 | { | ||
| 1591 | return $this->generatorType === self::GENERATOR_TYPE_NONE; | ||
| 1592 | } | ||
| 1593 | |||
| 1594 | /** | ||
| 1595 | * Gets the type of a field. | ||
| 1596 | * | ||
| 1597 | * @todo 3.0 Remove this. PersisterHelper should fix it somehow | ||
| 1598 | */ | ||
| 1599 | public function getTypeOfField(string $fieldName): string|null | ||
| 1600 | { | ||
| 1601 | return isset($this->fieldMappings[$fieldName]) | ||
| 1602 | ? $this->fieldMappings[$fieldName]->type | ||
| 1603 | : null; | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | /** | ||
| 1607 | * Gets the name of the primary table. | ||
| 1608 | */ | ||
| 1609 | public function getTableName(): string | ||
| 1610 | { | ||
| 1611 | return $this->table['name']; | ||
| 1612 | } | ||
| 1613 | |||
| 1614 | /** | ||
| 1615 | * Gets primary table's schema name. | ||
| 1616 | */ | ||
| 1617 | public function getSchemaName(): string|null | ||
| 1618 | { | ||
| 1619 | return $this->table['schema'] ?? null; | ||
| 1620 | } | ||
| 1621 | |||
| 1622 | /** | ||
| 1623 | * Gets the table name to use for temporary identifier tables of this class. | ||
| 1624 | */ | ||
| 1625 | public function getTemporaryIdTableName(): string | ||
| 1626 | { | ||
| 1627 | // replace dots with underscores because PostgreSQL creates temporary tables in a special schema | ||
| 1628 | return str_replace('.', '_', $this->getTableName() . '_id_tmp'); | ||
| 1629 | } | ||
| 1630 | |||
| 1631 | /** | ||
| 1632 | * Sets the mapped subclasses of this class. | ||
| 1633 | * | ||
| 1634 | * @psalm-param list<string> $subclasses The names of all mapped subclasses. | ||
| 1635 | */ | ||
| 1636 | public function setSubclasses(array $subclasses): void | ||
| 1637 | { | ||
| 1638 | foreach ($subclasses as $subclass) { | ||
| 1639 | $this->subClasses[] = $this->fullyQualifiedClassName($subclass); | ||
| 1640 | } | ||
| 1641 | } | ||
| 1642 | |||
| 1643 | /** | ||
| 1644 | * Sets the parent class names. Only <em>entity</em> classes may be given. | ||
| 1645 | * | ||
| 1646 | * Assumes that the class names in the passed array are in the order: | ||
| 1647 | * directParent -> directParentParent -> directParentParentParent ... -> root. | ||
| 1648 | * | ||
| 1649 | * @psalm-param list<class-string> $classNames | ||
| 1650 | */ | ||
| 1651 | public function setParentClasses(array $classNames): void | ||
| 1652 | { | ||
| 1653 | $this->parentClasses = $classNames; | ||
| 1654 | |||
| 1655 | if (count($classNames) > 0) { | ||
| 1656 | $this->rootEntityName = array_pop($classNames); | ||
| 1657 | } | ||
| 1658 | } | ||
| 1659 | |||
| 1660 | /** | ||
| 1661 | * Sets the inheritance type used by the class and its subclasses. | ||
| 1662 | * | ||
| 1663 | * @psalm-param self::INHERITANCE_TYPE_* $type | ||
| 1664 | * | ||
| 1665 | * @throws MappingException | ||
| 1666 | */ | ||
| 1667 | public function setInheritanceType(int $type): void | ||
| 1668 | { | ||
| 1669 | if (! $this->isInheritanceType($type)) { | ||
| 1670 | throw MappingException::invalidInheritanceType($this->name, $type); | ||
| 1671 | } | ||
| 1672 | |||
| 1673 | $this->inheritanceType = $type; | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | /** | ||
| 1677 | * Sets the association to override association mapping of property for an entity relationship. | ||
| 1678 | * | ||
| 1679 | * @psalm-param array<string, mixed> $overrideMapping | ||
| 1680 | * | ||
| 1681 | * @throws MappingException | ||
| 1682 | */ | ||
| 1683 | public function setAssociationOverride(string $fieldName, array $overrideMapping): void | ||
| 1684 | { | ||
| 1685 | if (! isset($this->associationMappings[$fieldName])) { | ||
| 1686 | throw MappingException::invalidOverrideFieldName($this->name, $fieldName); | ||
| 1687 | } | ||
| 1688 | |||
| 1689 | $mapping = $this->associationMappings[$fieldName]->toArray(); | ||
| 1690 | |||
| 1691 | if (isset($mapping['inherited'])) { | ||
| 1692 | throw MappingException::illegalOverrideOfInheritedProperty( | ||
| 1693 | $this->name, | ||
| 1694 | $fieldName, | ||
| 1695 | $mapping['inherited'], | ||
| 1696 | ); | ||
| 1697 | } | ||
| 1698 | |||
| 1699 | if (isset($overrideMapping['joinColumns'])) { | ||
| 1700 | $mapping['joinColumns'] = $overrideMapping['joinColumns']; | ||
| 1701 | } | ||
| 1702 | |||
| 1703 | if (isset($overrideMapping['inversedBy'])) { | ||
| 1704 | $mapping['inversedBy'] = $overrideMapping['inversedBy']; | ||
| 1705 | } | ||
| 1706 | |||
| 1707 | if (isset($overrideMapping['joinTable'])) { | ||
| 1708 | $mapping['joinTable'] = $overrideMapping['joinTable']; | ||
| 1709 | } | ||
| 1710 | |||
| 1711 | if (isset($overrideMapping['fetch'])) { | ||
| 1712 | $mapping['fetch'] = $overrideMapping['fetch']; | ||
| 1713 | } | ||
| 1714 | |||
| 1715 | switch ($mapping['type']) { | ||
| 1716 | case self::ONE_TO_ONE: | ||
| 1717 | case self::MANY_TO_ONE: | ||
| 1718 | $mapping['joinColumnFieldNames'] = []; | ||
| 1719 | $mapping['sourceToTargetKeyColumns'] = []; | ||
| 1720 | break; | ||
| 1721 | case self::MANY_TO_MANY: | ||
| 1722 | $mapping['relationToSourceKeyColumns'] = []; | ||
| 1723 | $mapping['relationToTargetKeyColumns'] = []; | ||
| 1724 | break; | ||
| 1725 | } | ||
| 1726 | |||
| 1727 | $this->associationMappings[$fieldName] = $this->_validateAndCompleteAssociationMapping($mapping); | ||
| 1728 | } | ||
| 1729 | |||
| 1730 | /** | ||
| 1731 | * Sets the override for a mapped field. | ||
| 1732 | * | ||
| 1733 | * @psalm-param array<string, mixed> $overrideMapping | ||
| 1734 | * | ||
| 1735 | * @throws MappingException | ||
| 1736 | */ | ||
| 1737 | public function setAttributeOverride(string $fieldName, array $overrideMapping): void | ||
| 1738 | { | ||
| 1739 | if (! isset($this->fieldMappings[$fieldName])) { | ||
| 1740 | throw MappingException::invalidOverrideFieldName($this->name, $fieldName); | ||
| 1741 | } | ||
| 1742 | |||
| 1743 | $mapping = $this->fieldMappings[$fieldName]; | ||
| 1744 | |||
| 1745 | if (isset($mapping->inherited)) { | ||
| 1746 | throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName, $mapping->inherited); | ||
| 1747 | } | ||
| 1748 | |||
| 1749 | if (isset($mapping->id)) { | ||
| 1750 | $overrideMapping['id'] = $mapping->id; | ||
| 1751 | } | ||
| 1752 | |||
| 1753 | if (isset($mapping->declared)) { | ||
| 1754 | $overrideMapping['declared'] = $mapping->declared; | ||
| 1755 | } | ||
| 1756 | |||
| 1757 | if (! isset($overrideMapping['type'])) { | ||
| 1758 | $overrideMapping['type'] = $mapping->type; | ||
| 1759 | } | ||
| 1760 | |||
| 1761 | if (! isset($overrideMapping['fieldName'])) { | ||
| 1762 | $overrideMapping['fieldName'] = $mapping->fieldName; | ||
| 1763 | } | ||
| 1764 | |||
| 1765 | if ($overrideMapping['type'] !== $mapping->type) { | ||
| 1766 | throw MappingException::invalidOverrideFieldType($this->name, $fieldName); | ||
| 1767 | } | ||
| 1768 | |||
| 1769 | unset($this->fieldMappings[$fieldName]); | ||
| 1770 | unset($this->fieldNames[$mapping->columnName]); | ||
| 1771 | unset($this->columnNames[$mapping->fieldName]); | ||
| 1772 | |||
| 1773 | $overrideMapping = $this->validateAndCompleteFieldMapping($overrideMapping); | ||
| 1774 | |||
| 1775 | $this->fieldMappings[$fieldName] = $overrideMapping; | ||
| 1776 | } | ||
| 1777 | |||
| 1778 | /** | ||
| 1779 | * Checks whether a mapped field is inherited from an entity superclass. | ||
| 1780 | */ | ||
| 1781 | public function isInheritedField(string $fieldName): bool | ||
| 1782 | { | ||
| 1783 | return isset($this->fieldMappings[$fieldName]->inherited); | ||
| 1784 | } | ||
| 1785 | |||
| 1786 | /** | ||
| 1787 | * Checks if this entity is the root in any entity-inheritance-hierarchy. | ||
| 1788 | */ | ||
| 1789 | public function isRootEntity(): bool | ||
| 1790 | { | ||
| 1791 | return $this->name === $this->rootEntityName; | ||
| 1792 | } | ||
| 1793 | |||
| 1794 | /** | ||
| 1795 | * Checks whether a mapped association field is inherited from a superclass. | ||
| 1796 | */ | ||
| 1797 | public function isInheritedAssociation(string $fieldName): bool | ||
| 1798 | { | ||
| 1799 | return isset($this->associationMappings[$fieldName]->inherited); | ||
| 1800 | } | ||
| 1801 | |||
| 1802 | public function isInheritedEmbeddedClass(string $fieldName): bool | ||
| 1803 | { | ||
| 1804 | return isset($this->embeddedClasses[$fieldName]->inherited); | ||
| 1805 | } | ||
| 1806 | |||
| 1807 | /** | ||
| 1808 | * Sets the name of the primary table the class is mapped to. | ||
| 1809 | * | ||
| 1810 | * @deprecated Use {@link setPrimaryTable}. | ||
| 1811 | */ | ||
| 1812 | public function setTableName(string $tableName): void | ||
| 1813 | { | ||
| 1814 | $this->table['name'] = $tableName; | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | /** | ||
| 1818 | * Sets the primary table definition. The provided array supports the | ||
| 1819 | * following structure: | ||
| 1820 | * | ||
| 1821 | * name => <tableName> (optional, defaults to class name) | ||
| 1822 | * indexes => array of indexes (optional) | ||
| 1823 | * uniqueConstraints => array of constraints (optional) | ||
| 1824 | * | ||
| 1825 | * If a key is omitted, the current value is kept. | ||
| 1826 | * | ||
| 1827 | * @psalm-param array<string, mixed> $table The table description. | ||
| 1828 | */ | ||
| 1829 | public function setPrimaryTable(array $table): void | ||
| 1830 | { | ||
| 1831 | if (isset($table['name'])) { | ||
| 1832 | // Split schema and table name from a table name like "myschema.mytable" | ||
| 1833 | if (str_contains($table['name'], '.')) { | ||
| 1834 | [$this->table['schema'], $table['name']] = explode('.', $table['name'], 2); | ||
| 1835 | } | ||
| 1836 | |||
| 1837 | if ($table['name'][0] === '`') { | ||
| 1838 | $table['name'] = trim($table['name'], '`'); | ||
| 1839 | $this->table['quoted'] = true; | ||
| 1840 | } | ||
| 1841 | |||
| 1842 | $this->table['name'] = $table['name']; | ||
| 1843 | } | ||
| 1844 | |||
| 1845 | if (isset($table['quoted'])) { | ||
| 1846 | $this->table['quoted'] = $table['quoted']; | ||
| 1847 | } | ||
| 1848 | |||
| 1849 | if (isset($table['schema'])) { | ||
| 1850 | $this->table['schema'] = $table['schema']; | ||
| 1851 | } | ||
| 1852 | |||
| 1853 | if (isset($table['indexes'])) { | ||
| 1854 | $this->table['indexes'] = $table['indexes']; | ||
| 1855 | } | ||
| 1856 | |||
| 1857 | if (isset($table['uniqueConstraints'])) { | ||
| 1858 | $this->table['uniqueConstraints'] = $table['uniqueConstraints']; | ||
| 1859 | } | ||
| 1860 | |||
| 1861 | if (isset($table['options'])) { | ||
| 1862 | $this->table['options'] = $table['options']; | ||
| 1863 | } | ||
| 1864 | } | ||
| 1865 | |||
| 1866 | /** | ||
| 1867 | * Checks whether the given type identifies an inheritance type. | ||
| 1868 | */ | ||
| 1869 | private function isInheritanceType(int $type): bool | ||
| 1870 | { | ||
| 1871 | return $type === self::INHERITANCE_TYPE_NONE || | ||
| 1872 | $type === self::INHERITANCE_TYPE_SINGLE_TABLE || | ||
| 1873 | $type === self::INHERITANCE_TYPE_JOINED; | ||
| 1874 | } | ||
| 1875 | |||
| 1876 | /** | ||
| 1877 | * Adds a mapped field to the class. | ||
| 1878 | * | ||
| 1879 | * @psalm-param array<string, mixed> $mapping The field mapping. | ||
| 1880 | * | ||
| 1881 | * @throws MappingException | ||
| 1882 | */ | ||
| 1883 | public function mapField(array $mapping): void | ||
| 1884 | { | ||
| 1885 | $mapping = $this->validateAndCompleteFieldMapping($mapping); | ||
| 1886 | $this->assertFieldNotMapped($mapping->fieldName); | ||
| 1887 | |||
| 1888 | if (isset($mapping->generated)) { | ||
| 1889 | $this->requiresFetchAfterChange = true; | ||
| 1890 | } | ||
| 1891 | |||
| 1892 | $this->fieldMappings[$mapping->fieldName] = $mapping; | ||
| 1893 | } | ||
| 1894 | |||
| 1895 | /** | ||
| 1896 | * INTERNAL: | ||
| 1897 | * Adds an association mapping without completing/validating it. | ||
| 1898 | * This is mainly used to add inherited association mappings to derived classes. | ||
| 1899 | * | ||
| 1900 | * @param ConcreteAssociationMapping $mapping | ||
| 1901 | * | ||
| 1902 | * @throws MappingException | ||
| 1903 | */ | ||
| 1904 | public function addInheritedAssociationMapping(AssociationMapping $mapping/*, $owningClassName = null*/): void | ||
| 1905 | { | ||
| 1906 | if (isset($this->associationMappings[$mapping->fieldName])) { | ||
| 1907 | throw MappingException::duplicateAssociationMapping($this->name, $mapping->fieldName); | ||
| 1908 | } | ||
| 1909 | |||
| 1910 | $this->associationMappings[$mapping->fieldName] = $mapping; | ||
| 1911 | } | ||
| 1912 | |||
| 1913 | /** | ||
| 1914 | * INTERNAL: | ||
| 1915 | * Adds a field mapping without completing/validating it. | ||
| 1916 | * This is mainly used to add inherited field mappings to derived classes. | ||
| 1917 | */ | ||
| 1918 | public function addInheritedFieldMapping(FieldMapping $fieldMapping): void | ||
| 1919 | { | ||
| 1920 | $this->fieldMappings[$fieldMapping->fieldName] = $fieldMapping; | ||
| 1921 | $this->columnNames[$fieldMapping->fieldName] = $fieldMapping->columnName; | ||
| 1922 | $this->fieldNames[$fieldMapping->columnName] = $fieldMapping->fieldName; | ||
| 1923 | |||
| 1924 | if (isset($fieldMapping->generated)) { | ||
| 1925 | $this->requiresFetchAfterChange = true; | ||
| 1926 | } | ||
| 1927 | } | ||
| 1928 | |||
| 1929 | /** | ||
| 1930 | * Adds a one-to-one mapping. | ||
| 1931 | * | ||
| 1932 | * @param array<string, mixed> $mapping The mapping. | ||
| 1933 | */ | ||
| 1934 | public function mapOneToOne(array $mapping): void | ||
| 1935 | { | ||
| 1936 | $mapping['type'] = self::ONE_TO_ONE; | ||
| 1937 | |||
| 1938 | $mapping = $this->_validateAndCompleteAssociationMapping($mapping); | ||
| 1939 | |||
| 1940 | $this->_storeAssociationMapping($mapping); | ||
| 1941 | } | ||
| 1942 | |||
| 1943 | /** | ||
| 1944 | * Adds a one-to-many mapping. | ||
| 1945 | * | ||
| 1946 | * @psalm-param array<string, mixed> $mapping The mapping. | ||
| 1947 | */ | ||
| 1948 | public function mapOneToMany(array $mapping): void | ||
| 1949 | { | ||
| 1950 | $mapping['type'] = self::ONE_TO_MANY; | ||
| 1951 | |||
| 1952 | $mapping = $this->_validateAndCompleteAssociationMapping($mapping); | ||
| 1953 | |||
| 1954 | $this->_storeAssociationMapping($mapping); | ||
| 1955 | } | ||
| 1956 | |||
| 1957 | /** | ||
| 1958 | * Adds a many-to-one mapping. | ||
| 1959 | * | ||
| 1960 | * @psalm-param array<string, mixed> $mapping The mapping. | ||
| 1961 | */ | ||
| 1962 | public function mapManyToOne(array $mapping): void | ||
| 1963 | { | ||
| 1964 | $mapping['type'] = self::MANY_TO_ONE; | ||
| 1965 | |||
| 1966 | $mapping = $this->_validateAndCompleteAssociationMapping($mapping); | ||
| 1967 | |||
| 1968 | $this->_storeAssociationMapping($mapping); | ||
| 1969 | } | ||
| 1970 | |||
| 1971 | /** | ||
| 1972 | * Adds a many-to-many mapping. | ||
| 1973 | * | ||
| 1974 | * @psalm-param array<string, mixed> $mapping The mapping. | ||
| 1975 | */ | ||
| 1976 | public function mapManyToMany(array $mapping): void | ||
| 1977 | { | ||
| 1978 | $mapping['type'] = self::MANY_TO_MANY; | ||
| 1979 | |||
| 1980 | $mapping = $this->_validateAndCompleteAssociationMapping($mapping); | ||
| 1981 | |||
| 1982 | $this->_storeAssociationMapping($mapping); | ||
| 1983 | } | ||
| 1984 | |||
| 1985 | /** | ||
| 1986 | * Stores the association mapping. | ||
| 1987 | * | ||
| 1988 | * @param ConcreteAssociationMapping $assocMapping | ||
| 1989 | * | ||
| 1990 | * @throws MappingException | ||
| 1991 | */ | ||
| 1992 | protected function _storeAssociationMapping(AssociationMapping $assocMapping): void | ||
| 1993 | { | ||
| 1994 | $sourceFieldName = $assocMapping->fieldName; | ||
| 1995 | |||
| 1996 | $this->assertFieldNotMapped($sourceFieldName); | ||
| 1997 | |||
| 1998 | $this->associationMappings[$sourceFieldName] = $assocMapping; | ||
| 1999 | } | ||
| 2000 | |||
| 2001 | /** | ||
| 2002 | * Registers a custom repository class for the entity class. | ||
| 2003 | * | ||
| 2004 | * @param string|null $repositoryClassName The class name of the custom mapper. | ||
| 2005 | * @psalm-param class-string<EntityRepository>|null $repositoryClassName | ||
| 2006 | */ | ||
| 2007 | public function setCustomRepositoryClass(string|null $repositoryClassName): void | ||
| 2008 | { | ||
| 2009 | if ($repositoryClassName === null) { | ||
| 2010 | $this->customRepositoryClassName = null; | ||
| 2011 | |||
| 2012 | return; | ||
| 2013 | } | ||
| 2014 | |||
| 2015 | $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName); | ||
| 2016 | } | ||
| 2017 | |||
| 2018 | /** | ||
| 2019 | * Dispatches the lifecycle event of the given entity to the registered | ||
| 2020 | * lifecycle callbacks and lifecycle listeners. | ||
| 2021 | * | ||
| 2022 | * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker | ||
| 2023 | * | ||
| 2024 | * @param string $lifecycleEvent The lifecycle event. | ||
| 2025 | */ | ||
| 2026 | public function invokeLifecycleCallbacks(string $lifecycleEvent, object $entity): void | ||
| 2027 | { | ||
| 2028 | foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { | ||
| 2029 | $entity->$callback(); | ||
| 2030 | } | ||
| 2031 | } | ||
| 2032 | |||
| 2033 | /** | ||
| 2034 | * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. | ||
| 2035 | */ | ||
| 2036 | public function hasLifecycleCallbacks(string $lifecycleEvent): bool | ||
| 2037 | { | ||
| 2038 | return isset($this->lifecycleCallbacks[$lifecycleEvent]); | ||
| 2039 | } | ||
| 2040 | |||
| 2041 | /** | ||
| 2042 | * Gets the registered lifecycle callbacks for an event. | ||
| 2043 | * | ||
| 2044 | * @return string[] | ||
| 2045 | * @psalm-return list<string> | ||
| 2046 | */ | ||
| 2047 | public function getLifecycleCallbacks(string $event): array | ||
| 2048 | { | ||
| 2049 | return $this->lifecycleCallbacks[$event] ?? []; | ||
| 2050 | } | ||
| 2051 | |||
| 2052 | /** | ||
| 2053 | * Adds a lifecycle callback for entities of this class. | ||
| 2054 | */ | ||
| 2055 | public function addLifecycleCallback(string $callback, string $event): void | ||
| 2056 | { | ||
| 2057 | if ($this->isEmbeddedClass) { | ||
| 2058 | throw MappingException::illegalLifecycleCallbackOnEmbeddedClass($callback, $this->name); | ||
| 2059 | } | ||
| 2060 | |||
| 2061 | if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) { | ||
| 2062 | return; | ||
| 2063 | } | ||
| 2064 | |||
| 2065 | $this->lifecycleCallbacks[$event][] = $callback; | ||
| 2066 | } | ||
| 2067 | |||
| 2068 | /** | ||
| 2069 | * Sets the lifecycle callbacks for entities of this class. | ||
| 2070 | * Any previously registered callbacks are overwritten. | ||
| 2071 | * | ||
| 2072 | * @psalm-param array<string, list<string>> $callbacks | ||
| 2073 | */ | ||
| 2074 | public function setLifecycleCallbacks(array $callbacks): void | ||
| 2075 | { | ||
| 2076 | $this->lifecycleCallbacks = $callbacks; | ||
| 2077 | } | ||
| 2078 | |||
| 2079 | /** | ||
| 2080 | * Adds a entity listener for entities of this class. | ||
| 2081 | * | ||
| 2082 | * @param string $eventName The entity lifecycle event. | ||
| 2083 | * @param string $class The listener class. | ||
| 2084 | * @param string $method The listener callback method. | ||
| 2085 | * | ||
| 2086 | * @throws MappingException | ||
| 2087 | */ | ||
| 2088 | public function addEntityListener(string $eventName, string $class, string $method): void | ||
| 2089 | { | ||
| 2090 | $class = $this->fullyQualifiedClassName($class); | ||
| 2091 | |||
| 2092 | $listener = [ | ||
| 2093 | 'class' => $class, | ||
| 2094 | 'method' => $method, | ||
| 2095 | ]; | ||
| 2096 | |||
| 2097 | if (! class_exists($class)) { | ||
| 2098 | throw MappingException::entityListenerClassNotFound($class, $this->name); | ||
| 2099 | } | ||
| 2100 | |||
| 2101 | if (! method_exists($class, $method)) { | ||
| 2102 | throw MappingException::entityListenerMethodNotFound($class, $method, $this->name); | ||
| 2103 | } | ||
| 2104 | |||
| 2105 | if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) { | ||
| 2106 | throw MappingException::duplicateEntityListener($class, $method, $this->name); | ||
| 2107 | } | ||
| 2108 | |||
| 2109 | $this->entityListeners[$eventName][] = $listener; | ||
| 2110 | } | ||
| 2111 | |||
| 2112 | /** | ||
| 2113 | * Sets the discriminator column definition. | ||
| 2114 | * | ||
| 2115 | * @see getDiscriminatorColumn() | ||
| 2116 | * | ||
| 2117 | * @param DiscriminatorColumnMapping|mixed[]|null $columnDef | ||
| 2118 | * @psalm-param DiscriminatorColumnMapping|array{ | ||
| 2119 | * name: string|null, | ||
| 2120 | * fieldName?: string|null, | ||
| 2121 | * type?: string|null, | ||
| 2122 | * length?: int|null, | ||
| 2123 | * columnDefinition?: string|null, | ||
| 2124 | * enumType?: class-string<BackedEnum>|null, | ||
| 2125 | * options?: array<string, mixed>|null | ||
| 2126 | * }|null $columnDef | ||
| 2127 | * | ||
| 2128 | * @throws MappingException | ||
| 2129 | */ | ||
| 2130 | public function setDiscriminatorColumn(DiscriminatorColumnMapping|array|null $columnDef): void | ||
| 2131 | { | ||
| 2132 | if ($columnDef instanceof DiscriminatorColumnMapping) { | ||
| 2133 | $this->discriminatorColumn = $columnDef; | ||
| 2134 | |||
| 2135 | return; | ||
| 2136 | } | ||
| 2137 | |||
| 2138 | if ($columnDef !== null) { | ||
| 2139 | if (! isset($columnDef['name'])) { | ||
| 2140 | throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); | ||
| 2141 | } | ||
| 2142 | |||
| 2143 | if (isset($this->fieldNames[$columnDef['name']])) { | ||
| 2144 | throw MappingException::duplicateColumnName($this->name, $columnDef['name']); | ||
| 2145 | } | ||
| 2146 | |||
| 2147 | $columnDef['fieldName'] ??= $columnDef['name']; | ||
| 2148 | $columnDef['type'] ??= 'string'; | ||
| 2149 | $columnDef['options'] ??= []; | ||
| 2150 | |||
| 2151 | if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) { | ||
| 2152 | throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); | ||
| 2153 | } | ||
| 2154 | |||
| 2155 | $this->discriminatorColumn = DiscriminatorColumnMapping::fromMappingArray($columnDef); | ||
| 2156 | } | ||
| 2157 | } | ||
| 2158 | |||
| 2159 | final public function getDiscriminatorColumn(): DiscriminatorColumnMapping | ||
| 2160 | { | ||
| 2161 | if ($this->discriminatorColumn === null) { | ||
| 2162 | throw new LogicException('The discriminator column was not set.'); | ||
| 2163 | } | ||
| 2164 | |||
| 2165 | return $this->discriminatorColumn; | ||
| 2166 | } | ||
| 2167 | |||
| 2168 | /** | ||
| 2169 | * Sets the discriminator values used by this class. | ||
| 2170 | * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. | ||
| 2171 | * | ||
| 2172 | * @param array<int|string, string> $map | ||
| 2173 | */ | ||
| 2174 | public function setDiscriminatorMap(array $map): void | ||
| 2175 | { | ||
| 2176 | foreach ($map as $value => $className) { | ||
| 2177 | $this->addDiscriminatorMapClass($value, $className); | ||
| 2178 | } | ||
| 2179 | } | ||
| 2180 | |||
| 2181 | /** | ||
| 2182 | * Adds one entry of the discriminator map with a new class and corresponding name. | ||
| 2183 | * | ||
| 2184 | * @throws MappingException | ||
| 2185 | */ | ||
| 2186 | public function addDiscriminatorMapClass(int|string $name, string $className): void | ||
| 2187 | { | ||
| 2188 | $className = $this->fullyQualifiedClassName($className); | ||
| 2189 | $className = ltrim($className, '\\'); | ||
| 2190 | |||
| 2191 | $this->discriminatorMap[$name] = $className; | ||
| 2192 | |||
| 2193 | if ($this->name === $className) { | ||
| 2194 | $this->discriminatorValue = $name; | ||
| 2195 | |||
| 2196 | return; | ||
| 2197 | } | ||
| 2198 | |||
| 2199 | if (! (class_exists($className) || interface_exists($className))) { | ||
| 2200 | throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); | ||
| 2201 | } | ||
| 2202 | |||
| 2203 | $this->addSubClass($className); | ||
| 2204 | } | ||
| 2205 | |||
| 2206 | /** @param array<class-string> $classes */ | ||
| 2207 | public function addSubClasses(array $classes): void | ||
| 2208 | { | ||
| 2209 | foreach ($classes as $className) { | ||
| 2210 | $this->addSubClass($className); | ||
| 2211 | } | ||
| 2212 | } | ||
| 2213 | |||
| 2214 | public function addSubClass(string $className): void | ||
| 2215 | { | ||
| 2216 | // By ignoring classes that are not subclasses of the current class, we simplify inheriting | ||
| 2217 | // the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata. | ||
| 2218 | |||
| 2219 | if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) { | ||
| 2220 | $this->subClasses[] = $className; | ||
| 2221 | } | ||
| 2222 | } | ||
| 2223 | |||
| 2224 | public function hasAssociation(string $fieldName): bool | ||
| 2225 | { | ||
| 2226 | return isset($this->associationMappings[$fieldName]); | ||
| 2227 | } | ||
| 2228 | |||
| 2229 | public function isSingleValuedAssociation(string $fieldName): bool | ||
| 2230 | { | ||
| 2231 | return isset($this->associationMappings[$fieldName]) | ||
| 2232 | && ($this->associationMappings[$fieldName]->isToOne()); | ||
| 2233 | } | ||
| 2234 | |||
| 2235 | public function isCollectionValuedAssociation(string $fieldName): bool | ||
| 2236 | { | ||
| 2237 | return isset($this->associationMappings[$fieldName]) | ||
| 2238 | && ! $this->associationMappings[$fieldName]->isToOne(); | ||
| 2239 | } | ||
| 2240 | |||
| 2241 | /** | ||
| 2242 | * Is this an association that only has a single join column? | ||
| 2243 | */ | ||
| 2244 | public function isAssociationWithSingleJoinColumn(string $fieldName): bool | ||
| 2245 | { | ||
| 2246 | return isset($this->associationMappings[$fieldName]) | ||
| 2247 | && isset($this->associationMappings[$fieldName]->joinColumns[0]) | ||
| 2248 | && ! isset($this->associationMappings[$fieldName]->joinColumns[1]); | ||
| 2249 | } | ||
| 2250 | |||
| 2251 | /** | ||
| 2252 | * Returns the single association join column (if any). | ||
| 2253 | * | ||
| 2254 | * @throws MappingException | ||
| 2255 | */ | ||
| 2256 | public function getSingleAssociationJoinColumnName(string $fieldName): string | ||
| 2257 | { | ||
| 2258 | if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { | ||
| 2259 | throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); | ||
| 2260 | } | ||
| 2261 | |||
| 2262 | $assoc = $this->associationMappings[$fieldName]; | ||
| 2263 | |||
| 2264 | assert($assoc->isToOneOwningSide()); | ||
| 2265 | |||
| 2266 | return $assoc->joinColumns[0]->name; | ||
| 2267 | } | ||
| 2268 | |||
| 2269 | /** | ||
| 2270 | * Returns the single association referenced join column name (if any). | ||
| 2271 | * | ||
| 2272 | * @throws MappingException | ||
| 2273 | */ | ||
| 2274 | public function getSingleAssociationReferencedJoinColumnName(string $fieldName): string | ||
| 2275 | { | ||
| 2276 | if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { | ||
| 2277 | throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); | ||
| 2278 | } | ||
| 2279 | |||
| 2280 | $assoc = $this->associationMappings[$fieldName]; | ||
| 2281 | |||
| 2282 | assert($assoc->isToOneOwningSide()); | ||
| 2283 | |||
| 2284 | return $assoc->joinColumns[0]->referencedColumnName; | ||
| 2285 | } | ||
| 2286 | |||
| 2287 | /** | ||
| 2288 | * Used to retrieve a fieldname for either field or association from a given column. | ||
| 2289 | * | ||
| 2290 | * This method is used in foreign-key as primary-key contexts. | ||
| 2291 | * | ||
| 2292 | * @throws MappingException | ||
| 2293 | */ | ||
| 2294 | public function getFieldForColumn(string $columnName): string | ||
| 2295 | { | ||
| 2296 | if (isset($this->fieldNames[$columnName])) { | ||
| 2297 | return $this->fieldNames[$columnName]; | ||
| 2298 | } | ||
| 2299 | |||
| 2300 | foreach ($this->associationMappings as $assocName => $mapping) { | ||
| 2301 | if ( | ||
| 2302 | $this->isAssociationWithSingleJoinColumn($assocName) && | ||
| 2303 | assert($this->associationMappings[$assocName]->isToOneOwningSide()) && | ||
| 2304 | $this->associationMappings[$assocName]->joinColumns[0]->name === $columnName | ||
| 2305 | ) { | ||
| 2306 | return $assocName; | ||
| 2307 | } | ||
| 2308 | } | ||
| 2309 | |||
| 2310 | throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); | ||
| 2311 | } | ||
| 2312 | |||
| 2313 | /** | ||
| 2314 | * Sets the ID generator used to generate IDs for instances of this class. | ||
| 2315 | */ | ||
| 2316 | public function setIdGenerator(AbstractIdGenerator $generator): void | ||
| 2317 | { | ||
| 2318 | $this->idGenerator = $generator; | ||
| 2319 | } | ||
| 2320 | |||
| 2321 | /** | ||
| 2322 | * Sets definition. | ||
| 2323 | * | ||
| 2324 | * @psalm-param array<string, string|null> $definition | ||
| 2325 | */ | ||
| 2326 | public function setCustomGeneratorDefinition(array $definition): void | ||
| 2327 | { | ||
| 2328 | $this->customGeneratorDefinition = $definition; | ||
| 2329 | } | ||
| 2330 | |||
| 2331 | /** | ||
| 2332 | * Sets the definition of the sequence ID generator for this class. | ||
| 2333 | * | ||
| 2334 | * The definition must have the following structure: | ||
| 2335 | * <code> | ||
| 2336 | * array( | ||
| 2337 | * 'sequenceName' => 'name', | ||
| 2338 | * 'allocationSize' => 20, | ||
| 2339 | * 'initialValue' => 1 | ||
| 2340 | * 'quoted' => 1 | ||
| 2341 | * ) | ||
| 2342 | * </code> | ||
| 2343 | * | ||
| 2344 | * @psalm-param array{sequenceName?: string, allocationSize?: int|string, initialValue?: int|string, quoted?: mixed} $definition | ||
| 2345 | * | ||
| 2346 | * @throws MappingException | ||
| 2347 | */ | ||
| 2348 | public function setSequenceGeneratorDefinition(array $definition): void | ||
| 2349 | { | ||
| 2350 | if (! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') { | ||
| 2351 | throw MappingException::missingSequenceName($this->name); | ||
| 2352 | } | ||
| 2353 | |||
| 2354 | if ($definition['sequenceName'][0] === '`') { | ||
| 2355 | $definition['sequenceName'] = trim($definition['sequenceName'], '`'); | ||
| 2356 | $definition['quoted'] = true; | ||
| 2357 | } | ||
| 2358 | |||
| 2359 | if (! isset($definition['allocationSize']) || trim((string) $definition['allocationSize']) === '') { | ||
| 2360 | $definition['allocationSize'] = '1'; | ||
| 2361 | } | ||
| 2362 | |||
| 2363 | if (! isset($definition['initialValue']) || trim((string) $definition['initialValue']) === '') { | ||
| 2364 | $definition['initialValue'] = '1'; | ||
| 2365 | } | ||
| 2366 | |||
| 2367 | $definition['allocationSize'] = (string) $definition['allocationSize']; | ||
| 2368 | $definition['initialValue'] = (string) $definition['initialValue']; | ||
| 2369 | |||
| 2370 | $this->sequenceGeneratorDefinition = $definition; | ||
| 2371 | } | ||
| 2372 | |||
| 2373 | /** | ||
| 2374 | * Sets the version field mapping used for versioning. Sets the default | ||
| 2375 | * value to use depending on the column type. | ||
| 2376 | * | ||
| 2377 | * @psalm-param array<string, mixed> $mapping The version field mapping array. | ||
| 2378 | * | ||
| 2379 | * @throws MappingException | ||
| 2380 | */ | ||
| 2381 | public function setVersionMapping(array &$mapping): void | ||
| 2382 | { | ||
| 2383 | $this->isVersioned = true; | ||
| 2384 | $this->versionField = $mapping['fieldName']; | ||
| 2385 | $this->requiresFetchAfterChange = true; | ||
| 2386 | |||
| 2387 | if (! isset($mapping['default'])) { | ||
| 2388 | if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) { | ||
| 2389 | $mapping['default'] = 1; | ||
| 2390 | } elseif ($mapping['type'] === 'datetime') { | ||
| 2391 | $mapping['default'] = 'CURRENT_TIMESTAMP'; | ||
| 2392 | } else { | ||
| 2393 | throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']); | ||
| 2394 | } | ||
| 2395 | } | ||
| 2396 | } | ||
| 2397 | |||
| 2398 | /** | ||
| 2399 | * Sets whether this class is to be versioned for optimistic locking. | ||
| 2400 | */ | ||
| 2401 | public function setVersioned(bool $bool): void | ||
| 2402 | { | ||
| 2403 | $this->isVersioned = $bool; | ||
| 2404 | |||
| 2405 | if ($bool) { | ||
| 2406 | $this->requiresFetchAfterChange = true; | ||
| 2407 | } | ||
| 2408 | } | ||
| 2409 | |||
| 2410 | /** | ||
| 2411 | * Sets the name of the field that is to be used for versioning if this class is | ||
| 2412 | * versioned for optimistic locking. | ||
| 2413 | */ | ||
| 2414 | public function setVersionField(string|null $versionField): void | ||
| 2415 | { | ||
| 2416 | $this->versionField = $versionField; | ||
| 2417 | } | ||
| 2418 | |||
| 2419 | /** | ||
| 2420 | * Marks this class as read only, no change tracking is applied to it. | ||
| 2421 | */ | ||
| 2422 | public function markReadOnly(): void | ||
| 2423 | { | ||
| 2424 | $this->isReadOnly = true; | ||
| 2425 | } | ||
| 2426 | |||
| 2427 | /** | ||
| 2428 | * {@inheritDoc} | ||
| 2429 | */ | ||
| 2430 | public function getFieldNames(): array | ||
| 2431 | { | ||
| 2432 | return array_keys($this->fieldMappings); | ||
| 2433 | } | ||
| 2434 | |||
| 2435 | /** | ||
| 2436 | * {@inheritDoc} | ||
| 2437 | */ | ||
| 2438 | public function getAssociationNames(): array | ||
| 2439 | { | ||
| 2440 | return array_keys($this->associationMappings); | ||
| 2441 | } | ||
| 2442 | |||
| 2443 | /** | ||
| 2444 | * {@inheritDoc} | ||
| 2445 | * | ||
| 2446 | * @psalm-return class-string | ||
| 2447 | * | ||
| 2448 | * @throws InvalidArgumentException | ||
| 2449 | */ | ||
| 2450 | public function getAssociationTargetClass(string $assocName): string | ||
| 2451 | { | ||
| 2452 | return $this->associationMappings[$assocName]->targetEntity | ||
| 2453 | ?? throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association."); | ||
| 2454 | } | ||
| 2455 | |||
| 2456 | public function getName(): string | ||
| 2457 | { | ||
| 2458 | return $this->name; | ||
| 2459 | } | ||
| 2460 | |||
| 2461 | public function isAssociationInverseSide(string $assocName): bool | ||
| 2462 | { | ||
| 2463 | return isset($this->associationMappings[$assocName]) | ||
| 2464 | && ! $this->associationMappings[$assocName]->isOwningSide(); | ||
| 2465 | } | ||
| 2466 | |||
| 2467 | public function getAssociationMappedByTargetField(string $assocName): string | ||
| 2468 | { | ||
| 2469 | $assoc = $this->getAssociationMapping($assocName); | ||
| 2470 | |||
| 2471 | if (! $assoc instanceof InverseSideMapping) { | ||
| 2472 | throw new LogicException(sprintf( | ||
| 2473 | <<<'EXCEPTION' | ||
| 2474 | Context: Calling %s() with "%s", which is the owning side of an association. | ||
| 2475 | Problem: The owning side of an association has no "mappedBy" field. | ||
| 2476 | Solution: Call %s::isAssociationInverseSide() to check first. | ||
| 2477 | EXCEPTION, | ||
| 2478 | __METHOD__, | ||
| 2479 | $assocName, | ||
| 2480 | self::class, | ||
| 2481 | )); | ||
| 2482 | } | ||
| 2483 | |||
| 2484 | return $assoc->mappedBy; | ||
| 2485 | } | ||
| 2486 | |||
| 2487 | /** | ||
| 2488 | * @param C $className | ||
| 2489 | * | ||
| 2490 | * @return string|null null if and only if the input value is null | ||
| 2491 | * @psalm-return (C is class-string ? class-string : (C is string ? string : null)) | ||
| 2492 | * | ||
| 2493 | * @template C of string|null | ||
| 2494 | */ | ||
| 2495 | public function fullyQualifiedClassName(string|null $className): string|null | ||
| 2496 | { | ||
| 2497 | if ($className === null) { | ||
| 2498 | Deprecation::trigger( | ||
| 2499 | 'doctrine/orm', | ||
| 2500 | 'https://github.com/doctrine/orm/pull/11294', | ||
| 2501 | 'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0', | ||
| 2502 | __METHOD__, | ||
| 2503 | ); | ||
| 2504 | |||
| 2505 | return null; | ||
| 2506 | } | ||
| 2507 | |||
| 2508 | if (! str_contains($className, '\\') && $this->namespace) { | ||
| 2509 | return $this->namespace . '\\' . $className; | ||
| 2510 | } | ||
| 2511 | |||
| 2512 | return $className; | ||
| 2513 | } | ||
| 2514 | |||
| 2515 | public function getMetadataValue(string $name): mixed | ||
| 2516 | { | ||
| 2517 | return $this->$name ?? null; | ||
| 2518 | } | ||
| 2519 | |||
| 2520 | /** | ||
| 2521 | * Map Embedded Class | ||
| 2522 | * | ||
| 2523 | * @psalm-param array{ | ||
| 2524 | * fieldName: string, | ||
| 2525 | * class?: class-string, | ||
| 2526 | * declaredField?: string, | ||
| 2527 | * columnPrefix?: string|false|null, | ||
| 2528 | * originalField?: string | ||
| 2529 | * } $mapping | ||
| 2530 | * | ||
| 2531 | * @throws MappingException | ||
| 2532 | */ | ||
| 2533 | public function mapEmbedded(array $mapping): void | ||
| 2534 | { | ||
| 2535 | $this->assertFieldNotMapped($mapping['fieldName']); | ||
| 2536 | |||
| 2537 | if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) { | ||
| 2538 | $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); | ||
| 2539 | if ($type instanceof ReflectionNamedType) { | ||
| 2540 | $mapping['class'] = $type->getName(); | ||
| 2541 | } | ||
| 2542 | } | ||
| 2543 | |||
| 2544 | if (! (isset($mapping['class']) && $mapping['class'])) { | ||
| 2545 | throw MappingException::missingEmbeddedClass($mapping['fieldName']); | ||
| 2546 | } | ||
| 2547 | |||
| 2548 | $this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([ | ||
| 2549 | 'class' => $this->fullyQualifiedClassName($mapping['class']), | ||
| 2550 | 'columnPrefix' => $mapping['columnPrefix'] ?? null, | ||
| 2551 | 'declaredField' => $mapping['declaredField'] ?? null, | ||
| 2552 | 'originalField' => $mapping['originalField'] ?? null, | ||
| 2553 | ]); | ||
| 2554 | } | ||
| 2555 | |||
| 2556 | /** | ||
| 2557 | * Inline the embeddable class | ||
| 2558 | */ | ||
| 2559 | public function inlineEmbeddable(string $property, ClassMetadata $embeddable): void | ||
| 2560 | { | ||
| 2561 | foreach ($embeddable->fieldMappings as $originalFieldMapping) { | ||
| 2562 | $fieldMapping = (array) $originalFieldMapping; | ||
| 2563 | $fieldMapping['originalClass'] ??= $embeddable->name; | ||
| 2564 | $fieldMapping['declaredField'] = isset($fieldMapping['declaredField']) | ||
| 2565 | ? $property . '.' . $fieldMapping['declaredField'] | ||
| 2566 | : $property; | ||
| 2567 | $fieldMapping['originalField'] ??= $fieldMapping['fieldName']; | ||
| 2568 | $fieldMapping['fieldName'] = $property . '.' . $fieldMapping['fieldName']; | ||
| 2569 | |||
| 2570 | if (! empty($this->embeddedClasses[$property]->columnPrefix)) { | ||
| 2571 | $fieldMapping['columnName'] = $this->embeddedClasses[$property]->columnPrefix . $fieldMapping['columnName']; | ||
| 2572 | } elseif ($this->embeddedClasses[$property]->columnPrefix !== false) { | ||
| 2573 | assert($this->reflClass !== null); | ||
| 2574 | assert($embeddable->reflClass !== null); | ||
| 2575 | $fieldMapping['columnName'] = $this->namingStrategy | ||
| 2576 | ->embeddedFieldToColumnName( | ||
| 2577 | $property, | ||
| 2578 | $fieldMapping['columnName'], | ||
| 2579 | $this->reflClass->name, | ||
| 2580 | $embeddable->reflClass->name, | ||
| 2581 | ); | ||
| 2582 | } | ||
| 2583 | |||
| 2584 | $this->mapField($fieldMapping); | ||
| 2585 | } | ||
| 2586 | } | ||
| 2587 | |||
| 2588 | /** @throws MappingException */ | ||
| 2589 | private function assertFieldNotMapped(string $fieldName): void | ||
| 2590 | { | ||
| 2591 | if ( | ||
| 2592 | isset($this->fieldMappings[$fieldName]) || | ||
| 2593 | isset($this->associationMappings[$fieldName]) || | ||
| 2594 | isset($this->embeddedClasses[$fieldName]) | ||
| 2595 | ) { | ||
| 2596 | throw MappingException::duplicateFieldMapping($this->name, $fieldName); | ||
| 2597 | } | ||
| 2598 | } | ||
| 2599 | |||
| 2600 | /** | ||
| 2601 | * Gets the sequence name based on class metadata. | ||
| 2602 | * | ||
| 2603 | * @todo Sequence names should be computed in DBAL depending on the platform | ||
| 2604 | */ | ||
| 2605 | public function getSequenceName(AbstractPlatform $platform): string | ||
| 2606 | { | ||
| 2607 | $sequencePrefix = $this->getSequencePrefix($platform); | ||
| 2608 | $columnName = $this->getSingleIdentifierColumnName(); | ||
| 2609 | |||
| 2610 | return $sequencePrefix . '_' . $columnName . '_seq'; | ||
| 2611 | } | ||
| 2612 | |||
| 2613 | /** | ||
| 2614 | * Gets the sequence name prefix based on class metadata. | ||
| 2615 | * | ||
| 2616 | * @todo Sequence names should be computed in DBAL depending on the platform | ||
| 2617 | */ | ||
| 2618 | public function getSequencePrefix(AbstractPlatform $platform): string | ||
| 2619 | { | ||
| 2620 | $tableName = $this->getTableName(); | ||
| 2621 | $sequencePrefix = $tableName; | ||
| 2622 | |||
| 2623 | // Prepend the schema name to the table name if there is one | ||
| 2624 | $schemaName = $this->getSchemaName(); | ||
| 2625 | if ($schemaName) { | ||
| 2626 | $sequencePrefix = $schemaName . '.' . $tableName; | ||
| 2627 | } | ||
| 2628 | |||
| 2629 | return $sequencePrefix; | ||
| 2630 | } | ||
| 2631 | |||
| 2632 | /** @psalm-param class-string $class */ | ||
| 2633 | private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ReflectionProperty|null | ||
| 2634 | { | ||
| 2635 | $reflectionProperty = $reflService->getAccessibleProperty($class, $field); | ||
| 2636 | if ($reflectionProperty?->isReadOnly()) { | ||
| 2637 | $declaringClass = $reflectionProperty->class; | ||
| 2638 | if ($declaringClass !== $class) { | ||
| 2639 | $reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field); | ||
| 2640 | } | ||
| 2641 | |||
| 2642 | if ($reflectionProperty !== null) { | ||
| 2643 | $reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty); | ||
| 2644 | } | ||
| 2645 | } | ||
| 2646 | |||
| 2647 | return $reflectionProperty; | ||
| 2648 | } | ||
| 2649 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\Common\EventManager; | ||
| 8 | use Doctrine\DBAL\Platforms; | ||
| 9 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
| 10 | use Doctrine\Deprecations\Deprecation; | ||
| 11 | use Doctrine\ORM\EntityManagerInterface; | ||
| 12 | use Doctrine\ORM\Event\LoadClassMetadataEventArgs; | ||
| 13 | use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs; | ||
| 14 | use Doctrine\ORM\Events; | ||
| 15 | use Doctrine\ORM\Exception\ORMException; | ||
| 16 | use Doctrine\ORM\Id\AssignedGenerator; | ||
| 17 | use Doctrine\ORM\Id\BigIntegerIdentityGenerator; | ||
| 18 | use Doctrine\ORM\Id\IdentityGenerator; | ||
| 19 | use Doctrine\ORM\Id\SequenceGenerator; | ||
| 20 | use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator; | ||
| 21 | use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType; | ||
| 22 | use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver; | ||
| 23 | use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; | ||
| 24 | use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface; | ||
| 25 | use Doctrine\Persistence\Mapping\Driver\MappingDriver; | ||
| 26 | use Doctrine\Persistence\Mapping\ReflectionService; | ||
| 27 | use ReflectionClass; | ||
| 28 | use ReflectionException; | ||
| 29 | |||
| 30 | use function assert; | ||
| 31 | use function class_exists; | ||
| 32 | use function count; | ||
| 33 | use function end; | ||
| 34 | use function explode; | ||
| 35 | use function in_array; | ||
| 36 | use function is_a; | ||
| 37 | use function is_subclass_of; | ||
| 38 | use function method_exists; | ||
| 39 | use function str_contains; | ||
| 40 | use function strlen; | ||
| 41 | use function strtolower; | ||
| 42 | use function substr; | ||
| 43 | |||
| 44 | /** | ||
| 45 | * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the | ||
| 46 | * metadata mapping information of a class which describes how a class should be mapped | ||
| 47 | * to a relational database. | ||
| 48 | * | ||
| 49 | * @extends AbstractClassMetadataFactory<ClassMetadata> | ||
| 50 | */ | ||
| 51 | class ClassMetadataFactory extends AbstractClassMetadataFactory | ||
| 52 | { | ||
| 53 | private EntityManagerInterface|null $em = null; | ||
| 54 | private AbstractPlatform|null $targetPlatform = null; | ||
| 55 | private MappingDriver|null $driver = null; | ||
| 56 | private EventManager|null $evm = null; | ||
| 57 | |||
| 58 | /** @var mixed[] */ | ||
| 59 | private array $embeddablesActiveNesting = []; | ||
| 60 | |||
| 61 | private const NON_IDENTITY_DEFAULT_STRATEGY = [ | ||
| 62 | Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE, | ||
| 63 | ]; | ||
| 64 | |||
| 65 | public function setEntityManager(EntityManagerInterface $em): void | ||
| 66 | { | ||
| 67 | parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver()); | ||
| 68 | |||
| 69 | $this->em = $em; | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * @param A $maybeOwningSide | ||
| 74 | * | ||
| 75 | * @return (A is ManyToManyAssociationMapping ? ManyToManyOwningSideMapping : ( | ||
| 76 | * A is OneToOneAssociationMapping ? OneToOneOwningSideMapping : ( | ||
| 77 | * A is OneToManyAssociationMapping ? ManyToOneAssociationMapping : ( | ||
| 78 | * A is ManyToOneAssociationMapping ? ManyToOneAssociationMapping : | ||
| 79 | * ManyToManyOwningSideMapping|OneToOneOwningSideMapping|ManyToOneAssociationMapping | ||
| 80 | * )))) | ||
| 81 | * | ||
| 82 | * @template A of AssociationMapping | ||
| 83 | */ | ||
| 84 | final public function getOwningSide(AssociationMapping $maybeOwningSide): OwningSideMapping | ||
| 85 | { | ||
| 86 | if ($maybeOwningSide instanceof OwningSideMapping) { | ||
| 87 | assert($maybeOwningSide instanceof ManyToManyOwningSideMapping || | ||
| 88 | $maybeOwningSide instanceof OneToOneOwningSideMapping || | ||
| 89 | $maybeOwningSide instanceof ManyToOneAssociationMapping); | ||
| 90 | |||
| 91 | return $maybeOwningSide; | ||
| 92 | } | ||
| 93 | |||
| 94 | assert($maybeOwningSide instanceof InverseSideMapping); | ||
| 95 | |||
| 96 | $owningSide = $this->getMetadataFor($maybeOwningSide->targetEntity) | ||
| 97 | ->associationMappings[$maybeOwningSide->mappedBy]; | ||
| 98 | |||
| 99 | assert($owningSide instanceof ManyToManyOwningSideMapping || | ||
| 100 | $owningSide instanceof OneToOneOwningSideMapping || | ||
| 101 | $owningSide instanceof ManyToOneAssociationMapping); | ||
| 102 | |||
| 103 | return $owningSide; | ||
| 104 | } | ||
| 105 | |||
| 106 | protected function initialize(): void | ||
| 107 | { | ||
| 108 | $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); | ||
| 109 | $this->evm = $this->em->getEventManager(); | ||
| 110 | $this->initialized = true; | ||
| 111 | } | ||
| 112 | |||
| 113 | protected function onNotFoundMetadata(string $className): ClassMetadata|null | ||
| 114 | { | ||
| 115 | if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) { | ||
| 116 | return null; | ||
| 117 | } | ||
| 118 | |||
| 119 | $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em); | ||
| 120 | |||
| 121 | $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs); | ||
| 122 | $classMetadata = $eventArgs->getFoundMetadata(); | ||
| 123 | assert($classMetadata instanceof ClassMetadata || $classMetadata === null); | ||
| 124 | |||
| 125 | return $classMetadata; | ||
| 126 | } | ||
| 127 | |||
| 128 | /** | ||
| 129 | * {@inheritDoc} | ||
| 130 | */ | ||
| 131 | protected function doLoadMetadata( | ||
| 132 | ClassMetadataInterface $class, | ||
| 133 | ClassMetadataInterface|null $parent, | ||
| 134 | bool $rootEntityFound, | ||
| 135 | array $nonSuperclassParents, | ||
| 136 | ): void { | ||
| 137 | if ($parent) { | ||
| 138 | $class->setInheritanceType($parent->inheritanceType); | ||
| 139 | $class->setDiscriminatorColumn($parent->discriminatorColumn === null ? null : clone $parent->discriminatorColumn); | ||
| 140 | $class->setIdGeneratorType($parent->generatorType); | ||
| 141 | $this->addInheritedFields($class, $parent); | ||
| 142 | $this->addInheritedRelations($class, $parent); | ||
| 143 | $this->addInheritedEmbeddedClasses($class, $parent); | ||
| 144 | $class->setIdentifier($parent->identifier); | ||
| 145 | $class->setVersioned($parent->isVersioned); | ||
| 146 | $class->setVersionField($parent->versionField); | ||
| 147 | $class->setDiscriminatorMap($parent->discriminatorMap); | ||
| 148 | $class->addSubClasses($parent->subClasses); | ||
| 149 | $class->setLifecycleCallbacks($parent->lifecycleCallbacks); | ||
| 150 | $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); | ||
| 151 | |||
| 152 | if (! empty($parent->customGeneratorDefinition)) { | ||
| 153 | $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition); | ||
| 154 | } | ||
| 155 | |||
| 156 | if ($parent->isMappedSuperclass) { | ||
| 157 | $class->setCustomRepositoryClass($parent->customRepositoryClassName); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | // Invoke driver | ||
| 162 | try { | ||
| 163 | $this->driver->loadMetadataForClass($class->getName(), $class); | ||
| 164 | } catch (ReflectionException $e) { | ||
| 165 | throw MappingException::reflectionFailure($class->getName(), $e); | ||
| 166 | } | ||
| 167 | |||
| 168 | // If this class has a parent the id generator strategy is inherited. | ||
| 169 | // However this is only true if the hierarchy of parents contains the root entity, | ||
| 170 | // if it consists of mapped superclasses these don't necessarily include the id field. | ||
| 171 | if ($parent && $rootEntityFound) { | ||
| 172 | $this->inheritIdGeneratorMapping($class, $parent); | ||
| 173 | } else { | ||
| 174 | $this->completeIdGeneratorMapping($class); | ||
| 175 | } | ||
| 176 | |||
| 177 | if (! $class->isMappedSuperclass) { | ||
| 178 | if ($rootEntityFound && $class->isInheritanceTypeNone()) { | ||
| 179 | throw MappingException::missingInheritanceTypeDeclaration(end($nonSuperclassParents), $class->name); | ||
| 180 | } | ||
| 181 | |||
| 182 | foreach ($class->embeddedClasses as $property => $embeddableClass) { | ||
| 183 | if (isset($embeddableClass->inherited)) { | ||
| 184 | continue; | ||
| 185 | } | ||
| 186 | |||
| 187 | if (isset($this->embeddablesActiveNesting[$embeddableClass->class])) { | ||
| 188 | throw MappingException::infiniteEmbeddableNesting($class->name, $property); | ||
| 189 | } | ||
| 190 | |||
| 191 | $this->embeddablesActiveNesting[$class->name] = true; | ||
| 192 | |||
| 193 | $embeddableMetadata = $this->getMetadataFor($embeddableClass->class); | ||
| 194 | |||
| 195 | if ($embeddableMetadata->isEmbeddedClass) { | ||
| 196 | $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property); | ||
| 197 | } | ||
| 198 | |||
| 199 | $identifier = $embeddableMetadata->getIdentifier(); | ||
| 200 | |||
| 201 | if (! empty($identifier)) { | ||
| 202 | $this->inheritIdGeneratorMapping($class, $embeddableMetadata); | ||
| 203 | } | ||
| 204 | |||
| 205 | $class->inlineEmbeddable($property, $embeddableMetadata); | ||
| 206 | |||
| 207 | unset($this->embeddablesActiveNesting[$class->name]); | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | if ($parent) { | ||
| 212 | if ($parent->isInheritanceTypeSingleTable()) { | ||
| 213 | $class->setPrimaryTable($parent->table); | ||
| 214 | } | ||
| 215 | |||
| 216 | $this->addInheritedIndexes($class, $parent); | ||
| 217 | |||
| 218 | if ($parent->cache) { | ||
| 219 | $class->cache = $parent->cache; | ||
| 220 | } | ||
| 221 | |||
| 222 | if ($parent->containsForeignIdentifier) { | ||
| 223 | $class->containsForeignIdentifier = true; | ||
| 224 | } | ||
| 225 | |||
| 226 | if ($parent->containsEnumIdentifier) { | ||
| 227 | $class->containsEnumIdentifier = true; | ||
| 228 | } | ||
| 229 | |||
| 230 | if (! empty($parent->entityListeners) && empty($class->entityListeners)) { | ||
| 231 | $class->entityListeners = $parent->entityListeners; | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | $class->setParentClasses($nonSuperclassParents); | ||
| 236 | |||
| 237 | if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { | ||
| 238 | $this->addDefaultDiscriminatorMap($class); | ||
| 239 | } | ||
| 240 | |||
| 241 | // During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402. | ||
| 242 | // So, we must not discover the missing subclasses before that. | ||
| 243 | |||
| 244 | if ($this->evm->hasListeners(Events::loadClassMetadata)) { | ||
| 245 | $eventArgs = new LoadClassMetadataEventArgs($class, $this->em); | ||
| 246 | $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); | ||
| 247 | } | ||
| 248 | |||
| 249 | $this->findAbstractEntityClassesNotListedInDiscriminatorMap($class); | ||
| 250 | |||
| 251 | $this->validateRuntimeMetadata($class, $parent); | ||
| 252 | } | ||
| 253 | |||
| 254 | /** | ||
| 255 | * Validate runtime metadata is correctly defined. | ||
| 256 | * | ||
| 257 | * @throws MappingException | ||
| 258 | */ | ||
| 259 | protected function validateRuntimeMetadata(ClassMetadata $class, ClassMetadataInterface|null $parent): void | ||
| 260 | { | ||
| 261 | if (! $class->reflClass) { | ||
| 262 | // only validate if there is a reflection class instance | ||
| 263 | return; | ||
| 264 | } | ||
| 265 | |||
| 266 | $class->validateIdentifier(); | ||
| 267 | $class->validateAssociations(); | ||
| 268 | $class->validateLifecycleCallbacks($this->getReflectionService()); | ||
| 269 | |||
| 270 | // verify inheritance | ||
| 271 | if (! $class->isMappedSuperclass && ! $class->isInheritanceTypeNone()) { | ||
| 272 | if (! $parent) { | ||
| 273 | if (count($class->discriminatorMap) === 0) { | ||
| 274 | throw MappingException::missingDiscriminatorMap($class->name); | ||
| 275 | } | ||
| 276 | |||
| 277 | if (! $class->discriminatorColumn) { | ||
| 278 | throw MappingException::missingDiscriminatorColumn($class->name); | ||
| 279 | } | ||
| 280 | |||
| 281 | foreach ($class->subClasses as $subClass) { | ||
| 282 | if ((new ReflectionClass($subClass))->name !== $subClass) { | ||
| 283 | throw MappingException::invalidClassInDiscriminatorMap($subClass, $class->name); | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } else { | ||
| 287 | assert($parent instanceof ClassMetadata); // https://github.com/doctrine/orm/issues/8746 | ||
| 288 | if ( | ||
| 289 | ! $class->reflClass->isAbstract() | ||
| 290 | && ! in_array($class->name, $class->discriminatorMap, true) | ||
| 291 | ) { | ||
| 292 | throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } elseif ($class->isMappedSuperclass && $class->name === $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { | ||
| 296 | // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy | ||
| 297 | throw MappingException::noInheritanceOnMappedSuperClass($class->name); | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | protected function newClassMetadataInstance(string $className): ClassMetadata | ||
| 302 | { | ||
| 303 | return new ClassMetadata( | ||
| 304 | $className, | ||
| 305 | $this->em->getConfiguration()->getNamingStrategy(), | ||
| 306 | $this->em->getConfiguration()->getTypedFieldMapper(), | ||
| 307 | ); | ||
| 308 | } | ||
| 309 | |||
| 310 | /** | ||
| 311 | * Adds a default discriminator map if no one is given | ||
| 312 | * | ||
| 313 | * If an entity is of any inheritance type and does not contain a | ||
| 314 | * discriminator map, then the map is generated automatically. This process | ||
| 315 | * is expensive computation wise. | ||
| 316 | * | ||
| 317 | * The automatically generated discriminator map contains the lowercase short name of | ||
| 318 | * each class as key. | ||
| 319 | * | ||
| 320 | * @throws MappingException | ||
| 321 | */ | ||
| 322 | private function addDefaultDiscriminatorMap(ClassMetadata $class): void | ||
| 323 | { | ||
| 324 | $allClasses = $this->driver->getAllClassNames(); | ||
| 325 | $fqcn = $class->getName(); | ||
| 326 | $map = [$this->getShortName($class->name) => $fqcn]; | ||
| 327 | |||
| 328 | $duplicates = []; | ||
| 329 | foreach ($allClasses as $subClassCandidate) { | ||
| 330 | if (is_subclass_of($subClassCandidate, $fqcn)) { | ||
| 331 | $shortName = $this->getShortName($subClassCandidate); | ||
| 332 | |||
| 333 | if (isset($map[$shortName])) { | ||
| 334 | $duplicates[] = $shortName; | ||
| 335 | } | ||
| 336 | |||
| 337 | $map[$shortName] = $subClassCandidate; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | if ($duplicates) { | ||
| 342 | throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); | ||
| 343 | } | ||
| 344 | |||
| 345 | $class->setDiscriminatorMap($map); | ||
| 346 | } | ||
| 347 | |||
| 348 | private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void | ||
| 349 | { | ||
| 350 | // Only root classes in inheritance hierarchies need contain a discriminator map, | ||
| 351 | // so skip for other classes. | ||
| 352 | if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) { | ||
| 353 | return; | ||
| 354 | } | ||
| 355 | |||
| 356 | $processedClasses = [$rootEntityClass->name => true]; | ||
| 357 | foreach ($rootEntityClass->subClasses as $knownSubClass) { | ||
| 358 | $processedClasses[$knownSubClass] = true; | ||
| 359 | } | ||
| 360 | |||
| 361 | foreach ($rootEntityClass->discriminatorMap as $declaredClassName) { | ||
| 362 | // This fetches non-transient parent classes only | ||
| 363 | $parentClasses = $this->getParentClasses($declaredClassName); | ||
| 364 | |||
| 365 | foreach ($parentClasses as $parentClass) { | ||
| 366 | if (isset($processedClasses[$parentClass])) { | ||
| 367 | continue; | ||
| 368 | } | ||
| 369 | |||
| 370 | $processedClasses[$parentClass] = true; | ||
| 371 | |||
| 372 | // All non-abstract entity classes must be listed in the discriminator map, and | ||
| 373 | // this will be validated/enforced at runtime (possibly at a later time, when the | ||
| 374 | // subclass is loaded, but anyways). Also, subclasses is about entity classes only. | ||
| 375 | // That means we can ignore non-abstract classes here. The (expensive) driver | ||
| 376 | // check for mapped superclasses need only be run for abstract candidate classes. | ||
| 377 | if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) { | ||
| 378 | continue; | ||
| 379 | } | ||
| 380 | |||
| 381 | // We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter) | ||
| 382 | $rootEntityClass->addSubClass($parentClass); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
| 386 | |||
| 387 | /** @param class-string $className */ | ||
| 388 | private function peekIfIsMappedSuperclass(string $className): bool | ||
| 389 | { | ||
| 390 | $reflService = $this->getReflectionService(); | ||
| 391 | $class = $this->newClassMetadataInstance($className); | ||
| 392 | $this->initializeReflection($class, $reflService); | ||
| 393 | |||
| 394 | $this->getDriver()->loadMetadataForClass($className, $class); | ||
| 395 | |||
| 396 | return $class->isMappedSuperclass; | ||
| 397 | } | ||
| 398 | |||
| 399 | /** | ||
| 400 | * Gets the lower-case short name of a class. | ||
| 401 | * | ||
| 402 | * @psalm-param class-string $className | ||
| 403 | */ | ||
| 404 | private function getShortName(string $className): string | ||
| 405 | { | ||
| 406 | if (! str_contains($className, '\\')) { | ||
| 407 | return strtolower($className); | ||
| 408 | } | ||
| 409 | |||
| 410 | $parts = explode('\\', $className); | ||
| 411 | |||
| 412 | return strtolower(end($parts)); | ||
| 413 | } | ||
| 414 | |||
| 415 | /** | ||
| 416 | * Puts the `inherited` and `declared` values into mapping information for fields, associations | ||
| 417 | * and embedded classes. | ||
| 418 | */ | ||
| 419 | private function addMappingInheritanceInformation( | ||
| 420 | AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping, | ||
| 421 | ClassMetadata $parentClass, | ||
| 422 | ): void { | ||
| 423 | if (! isset($mapping->inherited) && ! $parentClass->isMappedSuperclass) { | ||
| 424 | $mapping->inherited = $parentClass->name; | ||
| 425 | } | ||
| 426 | |||
| 427 | if (! isset($mapping->declared)) { | ||
| 428 | $mapping->declared = $parentClass->name; | ||
| 429 | } | ||
| 430 | } | ||
| 431 | |||
| 432 | /** | ||
| 433 | * Adds inherited fields to the subclass mapping. | ||
| 434 | */ | ||
| 435 | private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void | ||
| 436 | { | ||
| 437 | foreach ($parentClass->fieldMappings as $mapping) { | ||
| 438 | $subClassMapping = clone $mapping; | ||
| 439 | $this->addMappingInheritanceInformation($subClassMapping, $parentClass); | ||
| 440 | $subClass->addInheritedFieldMapping($subClassMapping); | ||
| 441 | } | ||
| 442 | |||
| 443 | foreach ($parentClass->reflFields as $name => $field) { | ||
| 444 | $subClass->reflFields[$name] = $field; | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | /** | ||
| 449 | * Adds inherited association mappings to the subclass mapping. | ||
| 450 | * | ||
| 451 | * @throws MappingException | ||
| 452 | */ | ||
| 453 | private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void | ||
| 454 | { | ||
| 455 | foreach ($parentClass->associationMappings as $field => $mapping) { | ||
| 456 | $subClassMapping = clone $mapping; | ||
| 457 | $this->addMappingInheritanceInformation($subClassMapping, $parentClass); | ||
| 458 | // When the class inheriting the relation ($subClass) is the first entity class since the | ||
| 459 | // relation has been defined in a mapped superclass (or in a chain | ||
| 460 | // of mapped superclasses) above, then declare this current entity class as the source of | ||
| 461 | // the relationship. | ||
| 462 | // According to the definitions given in https://github.com/doctrine/orm/pull/10396/, | ||
| 463 | // this is the case <=> ! isset($mapping['inherited']). | ||
| 464 | if (! isset($subClassMapping->inherited)) { | ||
| 465 | $subClassMapping->sourceEntity = $subClass->name; | ||
| 466 | } | ||
| 467 | |||
| 468 | $subClass->addInheritedAssociationMapping($subClassMapping); | ||
| 469 | } | ||
| 470 | } | ||
| 471 | |||
| 472 | private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void | ||
| 473 | { | ||
| 474 | foreach ($parentClass->embeddedClasses as $field => $embeddedClass) { | ||
| 475 | $subClassMapping = clone $embeddedClass; | ||
| 476 | $this->addMappingInheritanceInformation($subClassMapping, $parentClass); | ||
| 477 | $subClass->embeddedClasses[$field] = $subClassMapping; | ||
| 478 | } | ||
| 479 | } | ||
| 480 | |||
| 481 | /** | ||
| 482 | * Adds nested embedded classes metadata to a parent class. | ||
| 483 | * | ||
| 484 | * @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from. | ||
| 485 | * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to. | ||
| 486 | * @param string $prefix Embedded classes' prefix to use for nested embedded classes field names. | ||
| 487 | */ | ||
| 488 | private function addNestedEmbeddedClasses( | ||
| 489 | ClassMetadata $subClass, | ||
| 490 | ClassMetadata $parentClass, | ||
| 491 | string $prefix, | ||
| 492 | ): void { | ||
| 493 | foreach ($subClass->embeddedClasses as $property => $embeddableClass) { | ||
| 494 | if (isset($embeddableClass->inherited)) { | ||
| 495 | continue; | ||
| 496 | } | ||
| 497 | |||
| 498 | $embeddableMetadata = $this->getMetadataFor($embeddableClass->class); | ||
| 499 | |||
| 500 | $parentClass->mapEmbedded( | ||
| 501 | [ | ||
| 502 | 'fieldName' => $prefix . '.' . $property, | ||
| 503 | 'class' => $embeddableMetadata->name, | ||
| 504 | 'columnPrefix' => $embeddableClass->columnPrefix, | ||
| 505 | 'declaredField' => $embeddableClass->declaredField | ||
| 506 | ? $prefix . '.' . $embeddableClass->declaredField | ||
| 507 | : $prefix, | ||
| 508 | 'originalField' => $embeddableClass->originalField ?: $property, | ||
| 509 | ], | ||
| 510 | ); | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | /** | ||
| 515 | * Copy the table indices from the parent class superclass to the child class | ||
| 516 | */ | ||
| 517 | private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void | ||
| 518 | { | ||
| 519 | if (! $parentClass->isMappedSuperclass) { | ||
| 520 | return; | ||
| 521 | } | ||
| 522 | |||
| 523 | foreach (['uniqueConstraints', 'indexes'] as $indexType) { | ||
| 524 | if (isset($parentClass->table[$indexType])) { | ||
| 525 | foreach ($parentClass->table[$indexType] as $indexName => $index) { | ||
| 526 | if (isset($subClass->table[$indexType][$indexName])) { | ||
| 527 | continue; // Let the inheriting table override indices | ||
| 528 | } | ||
| 529 | |||
| 530 | $subClass->table[$indexType][$indexName] = $index; | ||
| 531 | } | ||
| 532 | } | ||
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | /** | ||
| 537 | * Completes the ID generator mapping. If "auto" is specified we choose the generator | ||
| 538 | * most appropriate for the targeted database platform. | ||
| 539 | * | ||
| 540 | * @throws ORMException | ||
| 541 | */ | ||
| 542 | private function completeIdGeneratorMapping(ClassMetadata $class): void | ||
| 543 | { | ||
| 544 | $idGenType = $class->generatorType; | ||
| 545 | if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) { | ||
| 546 | $class->setIdGeneratorType($this->determineIdGeneratorStrategy($this->getTargetPlatform())); | ||
| 547 | } | ||
| 548 | |||
| 549 | // Create & assign an appropriate ID generator instance | ||
| 550 | switch ($class->generatorType) { | ||
| 551 | case ClassMetadata::GENERATOR_TYPE_IDENTITY: | ||
| 552 | $sequenceName = null; | ||
| 553 | $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null; | ||
| 554 | $platform = $this->getTargetPlatform(); | ||
| 555 | |||
| 556 | $generator = $fieldName && $class->fieldMappings[$fieldName]->type === 'bigint' | ||
| 557 | ? new BigIntegerIdentityGenerator() | ||
| 558 | : new IdentityGenerator(); | ||
| 559 | |||
| 560 | $class->setIdGenerator($generator); | ||
| 561 | |||
| 562 | break; | ||
| 563 | |||
| 564 | case ClassMetadata::GENERATOR_TYPE_SEQUENCE: | ||
| 565 | // If there is no sequence definition yet, create a default definition | ||
| 566 | $definition = $class->sequenceGeneratorDefinition; | ||
| 567 | |||
| 568 | if (! $definition) { | ||
| 569 | $fieldName = $class->getSingleIdentifierFieldName(); | ||
| 570 | $sequenceName = $class->getSequenceName($this->getTargetPlatform()); | ||
| 571 | $quoted = isset($class->fieldMappings[$fieldName]->quoted) || isset($class->table['quoted']); | ||
| 572 | |||
| 573 | $definition = [ | ||
| 574 | 'sequenceName' => $this->truncateSequenceName($sequenceName), | ||
| 575 | 'allocationSize' => 1, | ||
| 576 | 'initialValue' => 1, | ||
| 577 | ]; | ||
| 578 | |||
| 579 | if ($quoted) { | ||
| 580 | $definition['quoted'] = true; | ||
| 581 | } | ||
| 582 | |||
| 583 | $class->setSequenceGeneratorDefinition($definition); | ||
| 584 | } | ||
| 585 | |||
| 586 | $sequenceGenerator = new SequenceGenerator( | ||
| 587 | $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()), | ||
| 588 | (int) $definition['allocationSize'], | ||
| 589 | ); | ||
| 590 | $class->setIdGenerator($sequenceGenerator); | ||
| 591 | break; | ||
| 592 | |||
| 593 | case ClassMetadata::GENERATOR_TYPE_NONE: | ||
| 594 | $class->setIdGenerator(new AssignedGenerator()); | ||
| 595 | break; | ||
| 596 | |||
| 597 | case ClassMetadata::GENERATOR_TYPE_CUSTOM: | ||
| 598 | $definition = $class->customGeneratorDefinition; | ||
| 599 | if ($definition === null) { | ||
| 600 | throw InvalidCustomGenerator::onClassNotConfigured(); | ||
| 601 | } | ||
| 602 | |||
| 603 | if (! class_exists($definition['class'])) { | ||
| 604 | throw InvalidCustomGenerator::onMissingClass($definition); | ||
| 605 | } | ||
| 606 | |||
| 607 | $class->setIdGenerator(new $definition['class']()); | ||
| 608 | break; | ||
| 609 | |||
| 610 | default: | ||
| 611 | throw UnknownGeneratorType::create($class->generatorType); | ||
| 612 | } | ||
| 613 | } | ||
| 614 | |||
| 615 | /** @psalm-return ClassMetadata::GENERATOR_TYPE_* */ | ||
| 616 | private function determineIdGeneratorStrategy(AbstractPlatform $platform): int | ||
| 617 | { | ||
| 618 | assert($this->em !== null); | ||
| 619 | foreach ($this->em->getConfiguration()->getIdentityGenerationPreferences() as $platformFamily => $strategy) { | ||
| 620 | if (is_a($platform, $platformFamily)) { | ||
| 621 | return $strategy; | ||
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | $nonIdentityDefaultStrategy = self::NON_IDENTITY_DEFAULT_STRATEGY; | ||
| 626 | |||
| 627 | // DBAL 3 | ||
| 628 | if (method_exists($platform, 'getIdentitySequenceName')) { | ||
| 629 | $nonIdentityDefaultStrategy[Platforms\PostgreSQLPlatform::class] = ClassMetadata::GENERATOR_TYPE_SEQUENCE; | ||
| 630 | } | ||
| 631 | |||
| 632 | foreach ($nonIdentityDefaultStrategy as $platformFamily => $strategy) { | ||
| 633 | if (is_a($platform, $platformFamily)) { | ||
| 634 | if ($platform instanceof Platforms\PostgreSQLPlatform) { | ||
| 635 | Deprecation::trigger( | ||
| 636 | 'doctrine/orm', | ||
| 637 | 'https://github.com/doctrine/orm/issues/8893', | ||
| 638 | <<<'DEPRECATION' | ||
| 639 | Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY | ||
| 640 | results in SERIAL, which is not recommended. | ||
| 641 | Instead, configure identifier generation strategies explicitly through | ||
| 642 | configuration. | ||
| 643 | We currently recommend "SEQUENCE" for "%s", when using DBAL 3, | ||
| 644 | and "IDENTITY" when using DBAL 4, | ||
| 645 | so you should probably use the following configuration before upgrading to DBAL 4, | ||
| 646 | and remove it after deploying that upgrade: | ||
| 647 | |||
| 648 | $configuration->setIdentityGenerationPreferences([ | ||
| 649 | "%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE, | ||
| 650 | ]); | ||
| 651 | |||
| 652 | DEPRECATION, | ||
| 653 | $platformFamily, | ||
| 654 | $platformFamily, | ||
| 655 | ); | ||
| 656 | } | ||
| 657 | |||
| 658 | return $strategy; | ||
| 659 | } | ||
| 660 | } | ||
| 661 | |||
| 662 | return ClassMetadata::GENERATOR_TYPE_IDENTITY; | ||
| 663 | } | ||
| 664 | |||
| 665 | private function truncateSequenceName(string $schemaElementName): string | ||
| 666 | { | ||
| 667 | $platform = $this->getTargetPlatform(); | ||
| 668 | if (! $platform instanceof Platforms\OraclePlatform) { | ||
| 669 | return $schemaElementName; | ||
| 670 | } | ||
| 671 | |||
| 672 | $maxIdentifierLength = $platform->getMaxIdentifierLength(); | ||
| 673 | |||
| 674 | if (strlen($schemaElementName) > $maxIdentifierLength) { | ||
| 675 | return substr($schemaElementName, 0, $maxIdentifierLength); | ||
| 676 | } | ||
| 677 | |||
| 678 | return $schemaElementName; | ||
| 679 | } | ||
| 680 | |||
| 681 | /** | ||
| 682 | * Inherits the ID generator mapping from a parent class. | ||
| 683 | */ | ||
| 684 | private function inheritIdGeneratorMapping(ClassMetadata $class, ClassMetadata $parent): void | ||
| 685 | { | ||
| 686 | if ($parent->isIdGeneratorSequence()) { | ||
| 687 | $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); | ||
| 688 | } | ||
| 689 | |||
| 690 | if ($parent->generatorType) { | ||
| 691 | $class->setIdGeneratorType($parent->generatorType); | ||
| 692 | } | ||
| 693 | |||
| 694 | if ($parent->idGenerator ?? null) { | ||
| 695 | $class->setIdGenerator($parent->idGenerator); | ||
| 696 | } | ||
| 697 | } | ||
| 698 | |||
| 699 | protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService): void | ||
| 700 | { | ||
| 701 | $class->wakeupReflection($reflService); | ||
| 702 | } | ||
| 703 | |||
| 704 | protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService): void | ||
| 705 | { | ||
| 706 | $class->initializeReflection($reflService); | ||
| 707 | } | ||
| 708 | |||
| 709 | protected function getDriver(): MappingDriver | ||
| 710 | { | ||
| 711 | assert($this->driver !== null); | ||
| 712 | |||
| 713 | return $this->driver; | ||
| 714 | } | ||
| 715 | |||
| 716 | protected function isEntity(ClassMetadataInterface $class): bool | ||
| 717 | { | ||
| 718 | return ! $class->isMappedSuperclass; | ||
| 719 | } | ||
| 720 | |||
| 721 | private function getTargetPlatform(): Platforms\AbstractPlatform | ||
| 722 | { | ||
| 723 | if (! $this->targetPlatform) { | ||
| 724 | $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); | ||
| 725 | } | ||
| 726 | |||
| 727 | return $this->targetPlatform; | ||
| 728 | } | ||
| 729 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | use BackedEnum; | ||
| 9 | |||
| 10 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 11 | final class Column implements MappingAttribute | ||
| 12 | { | ||
| 13 | /** | ||
| 14 | * @param int|null $precision The precision for a decimal (exact numeric) column (Applies only for decimal column). | ||
| 15 | * @param int|null $scale The scale for a decimal (exact numeric) column (Applies only for decimal column). | ||
| 16 | * @param class-string<BackedEnum>|null $enumType | ||
| 17 | * @param array<string,mixed> $options | ||
| 18 | * @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated | ||
| 19 | */ | ||
| 20 | public function __construct( | ||
| 21 | public readonly string|null $name = null, | ||
| 22 | public readonly string|null $type = null, | ||
| 23 | public readonly int|null $length = null, | ||
| 24 | public readonly int|null $precision = null, | ||
| 25 | public readonly int|null $scale = null, | ||
| 26 | public readonly bool $unique = false, | ||
| 27 | public readonly bool $nullable = false, | ||
| 28 | public readonly bool $insertable = true, | ||
| 29 | public readonly bool $updatable = true, | ||
| 30 | public readonly string|null $enumType = null, | ||
| 31 | public readonly array $options = [], | ||
| 32 | public readonly string|null $columnDefinition = null, | ||
| 33 | public readonly string|null $generated = null, | ||
| 34 | ) { | ||
| 35 | } | ||
| 36 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class CustomIdGenerator implements MappingAttribute | ||
| 11 | { | ||
| 12 | public function __construct( | ||
| 13 | public readonly string|null $class = null, | ||
| 14 | ) { | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/DefaultEntityListenerResolver.php b/vendor/doctrine/orm/src/Mapping/DefaultEntityListenerResolver.php new file mode 100644 index 0000000..0b3e7a2 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/DefaultEntityListenerResolver.php | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use function trim; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * The default DefaultEntityListener | ||
| 11 | */ | ||
| 12 | class DefaultEntityListenerResolver implements EntityListenerResolver | ||
| 13 | { | ||
| 14 | /** @psalm-var array<class-string, object> Map to store entity listener instances. */ | ||
| 15 | private array $instances = []; | ||
| 16 | |||
| 17 | public function clear(string|null $className = null): void | ||
| 18 | { | ||
| 19 | if ($className === null) { | ||
| 20 | $this->instances = []; | ||
| 21 | |||
| 22 | return; | ||
| 23 | } | ||
| 24 | |||
| 25 | $className = trim($className, '\\'); | ||
| 26 | unset($this->instances[$className]); | ||
| 27 | } | ||
| 28 | |||
| 29 | public function register(object $object): void | ||
| 30 | { | ||
| 31 | $this->instances[$object::class] = $object; | ||
| 32 | } | ||
| 33 | |||
| 34 | public function resolve(string $className): object | ||
| 35 | { | ||
| 36 | $className = trim($className, '\\'); | ||
| 37 | |||
| 38 | return $this->instances[$className] ??= new $className(); | ||
| 39 | } | ||
| 40 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use function str_contains; | ||
| 8 | use function strrpos; | ||
| 9 | use function strtolower; | ||
| 10 | use function substr; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * The default NamingStrategy | ||
| 14 | * | ||
| 15 | * @link www.doctrine-project.org | ||
| 16 | */ | ||
| 17 | class DefaultNamingStrategy implements NamingStrategy | ||
| 18 | { | ||
| 19 | public function classToTableName(string $className): string | ||
| 20 | { | ||
| 21 | if (str_contains($className, '\\')) { | ||
| 22 | return substr($className, strrpos($className, '\\') + 1); | ||
| 23 | } | ||
| 24 | |||
| 25 | return $className; | ||
| 26 | } | ||
| 27 | |||
| 28 | public function propertyToColumnName(string $propertyName, string $className): string | ||
| 29 | { | ||
| 30 | return $propertyName; | ||
| 31 | } | ||
| 32 | |||
| 33 | public function embeddedFieldToColumnName( | ||
| 34 | string $propertyName, | ||
| 35 | string $embeddedColumnName, | ||
| 36 | string $className, | ||
| 37 | string $embeddedClassName, | ||
| 38 | ): string { | ||
| 39 | return $propertyName . '_' . $embeddedColumnName; | ||
| 40 | } | ||
| 41 | |||
| 42 | public function referenceColumnName(): string | ||
| 43 | { | ||
| 44 | return 'id'; | ||
| 45 | } | ||
| 46 | |||
| 47 | public function joinColumnName(string $propertyName, string $className): string | ||
| 48 | { | ||
| 49 | return $propertyName . '_' . $this->referenceColumnName(); | ||
| 50 | } | ||
| 51 | |||
| 52 | public function joinTableName( | ||
| 53 | string $sourceEntity, | ||
| 54 | string $targetEntity, | ||
| 55 | string $propertyName, | ||
| 56 | ): string { | ||
| 57 | return strtolower($this->classToTableName($sourceEntity) . '_' . | ||
| 58 | $this->classToTableName($targetEntity)); | ||
| 59 | } | ||
| 60 | |||
| 61 | public function joinKeyColumnName( | ||
| 62 | string $entityName, | ||
| 63 | string|null $referencedColumnName, | ||
| 64 | ): string { | ||
| 65 | return strtolower($this->classToTableName($entityName) . '_' . | ||
| 66 | ($referencedColumnName ?: $this->referenceColumnName())); | ||
| 67 | } | ||
| 68 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
| 8 | use Doctrine\ORM\Internal\SQLResultCasing; | ||
| 9 | |||
| 10 | use function array_map; | ||
| 11 | use function array_merge; | ||
| 12 | use function assert; | ||
| 13 | use function is_numeric; | ||
| 14 | use function preg_replace; | ||
| 15 | use function substr; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * A set of rules for determining the physical column, alias and table quotes | ||
| 19 | */ | ||
| 20 | class DefaultQuoteStrategy implements QuoteStrategy | ||
| 21 | { | ||
| 22 | use SQLResultCasing; | ||
| 23 | |||
| 24 | public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string | ||
| 25 | { | ||
| 26 | return isset($class->fieldMappings[$fieldName]->quoted) | ||
| 27 | ? $platform->quoteIdentifier($class->fieldMappings[$fieldName]->columnName) | ||
| 28 | : $class->fieldMappings[$fieldName]->columnName; | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * {@inheritDoc} | ||
| 33 | * | ||
| 34 | * @todo Table names should be computed in DBAL depending on the platform | ||
| 35 | */ | ||
| 36 | public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string | ||
| 37 | { | ||
| 38 | $tableName = $class->table['name']; | ||
| 39 | |||
| 40 | if (! empty($class->table['schema'])) { | ||
| 41 | $tableName = $class->table['schema'] . '.' . $class->table['name']; | ||
| 42 | } | ||
| 43 | |||
| 44 | return isset($class->table['quoted']) | ||
| 45 | ? $platform->quoteIdentifier($tableName) | ||
| 46 | : $tableName; | ||
| 47 | } | ||
| 48 | |||
| 49 | /** | ||
| 50 | * {@inheritDoc} | ||
| 51 | */ | ||
| 52 | public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string | ||
| 53 | { | ||
| 54 | return isset($definition['quoted']) | ||
| 55 | ? $platform->quoteIdentifier($definition['sequenceName']) | ||
| 56 | : $definition['sequenceName']; | ||
| 57 | } | ||
| 58 | |||
| 59 | public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string | ||
| 60 | { | ||
| 61 | return isset($joinColumn->quoted) | ||
| 62 | ? $platform->quoteIdentifier($joinColumn->name) | ||
| 63 | : $joinColumn->name; | ||
| 64 | } | ||
| 65 | |||
| 66 | public function getReferencedJoinColumnName( | ||
| 67 | JoinColumnMapping $joinColumn, | ||
| 68 | ClassMetadata $class, | ||
| 69 | AbstractPlatform $platform, | ||
| 70 | ): string { | ||
| 71 | return isset($joinColumn->quoted) | ||
| 72 | ? $platform->quoteIdentifier($joinColumn->referencedColumnName) | ||
| 73 | : $joinColumn->referencedColumnName; | ||
| 74 | } | ||
| 75 | |||
| 76 | public function getJoinTableName( | ||
| 77 | ManyToManyOwningSideMapping $association, | ||
| 78 | ClassMetadata $class, | ||
| 79 | AbstractPlatform $platform, | ||
| 80 | ): string { | ||
| 81 | $schema = ''; | ||
| 82 | |||
| 83 | if (isset($association->joinTable->schema)) { | ||
| 84 | $schema = $association->joinTable->schema . '.'; | ||
| 85 | } | ||
| 86 | |||
| 87 | $tableName = $association->joinTable->name; | ||
| 88 | |||
| 89 | if (isset($association->joinTable->quoted)) { | ||
| 90 | $tableName = $platform->quoteIdentifier($tableName); | ||
| 91 | } | ||
| 92 | |||
| 93 | return $schema . $tableName; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * {@inheritDoc} | ||
| 98 | */ | ||
| 99 | public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array | ||
| 100 | { | ||
| 101 | $quotedColumnNames = []; | ||
| 102 | |||
| 103 | foreach ($class->identifier as $fieldName) { | ||
| 104 | if (isset($class->fieldMappings[$fieldName])) { | ||
| 105 | $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform); | ||
| 106 | |||
| 107 | continue; | ||
| 108 | } | ||
| 109 | |||
| 110 | // Association defined as Id field | ||
| 111 | $assoc = $class->associationMappings[$fieldName]; | ||
| 112 | assert($assoc->isToOneOwningSide()); | ||
| 113 | $joinColumns = $assoc->joinColumns; | ||
| 114 | $assocQuotedColumnNames = array_map( | ||
| 115 | static fn (JoinColumnMapping $joinColumn) => isset($joinColumn->quoted) | ||
| 116 | ? $platform->quoteIdentifier($joinColumn->name) | ||
| 117 | : $joinColumn->name, | ||
| 118 | $joinColumns, | ||
| 119 | ); | ||
| 120 | |||
| 121 | $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); | ||
| 122 | } | ||
| 123 | |||
| 124 | return $quotedColumnNames; | ||
| 125 | } | ||
| 126 | |||
| 127 | public function getColumnAlias( | ||
| 128 | string $columnName, | ||
| 129 | int $counter, | ||
| 130 | AbstractPlatform $platform, | ||
| 131 | ClassMetadata|null $class = null, | ||
| 132 | ): string { | ||
| 133 | // 1 ) Concatenate column name and counter | ||
| 134 | // 2 ) Trim the column alias to the maximum identifier length of the platform. | ||
| 135 | // If the alias is to long, characters are cut off from the beginning. | ||
| 136 | // 3 ) Strip non alphanumeric characters | ||
| 137 | // 4 ) Prefix with "_" if the result its numeric | ||
| 138 | $columnName .= '_' . $counter; | ||
| 139 | $columnName = substr($columnName, -$platform->getMaxIdentifierLength()); | ||
| 140 | $columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName); | ||
| 141 | $columnName = is_numeric($columnName) ? '_' . $columnName : $columnName; | ||
| 142 | |||
| 143 | return $this->getSQLResultCasing($platform, $columnName); | ||
| 144 | } | ||
| 145 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use DateInterval; | ||
| 9 | use DateTime; | ||
| 10 | use DateTimeImmutable; | ||
| 11 | use Doctrine\DBAL\Types\Type; | ||
| 12 | use Doctrine\DBAL\Types\Types; | ||
| 13 | use ReflectionEnum; | ||
| 14 | use ReflectionNamedType; | ||
| 15 | use ReflectionProperty; | ||
| 16 | |||
| 17 | use function array_merge; | ||
| 18 | use function assert; | ||
| 19 | use function enum_exists; | ||
| 20 | use function is_a; | ||
| 21 | |||
| 22 | /** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */ | ||
| 23 | final class DefaultTypedFieldMapper implements TypedFieldMapper | ||
| 24 | { | ||
| 25 | /** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */ | ||
| 26 | private array $typedFieldMappings; | ||
| 27 | |||
| 28 | private const DEFAULT_TYPED_FIELD_MAPPINGS = [ | ||
| 29 | DateInterval::class => Types::DATEINTERVAL, | ||
| 30 | DateTime::class => Types::DATETIME_MUTABLE, | ||
| 31 | DateTimeImmutable::class => Types::DATETIME_IMMUTABLE, | ||
| 32 | 'array' => Types::JSON, | ||
| 33 | 'bool' => Types::BOOLEAN, | ||
| 34 | 'float' => Types::FLOAT, | ||
| 35 | 'int' => Types::INTEGER, | ||
| 36 | 'string' => Types::STRING, | ||
| 37 | ]; | ||
| 38 | |||
| 39 | /** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */ | ||
| 40 | public function __construct(array $typedFieldMappings = []) | ||
| 41 | { | ||
| 42 | $this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings); | ||
| 43 | } | ||
| 44 | |||
| 45 | /** | ||
| 46 | * {@inheritDoc} | ||
| 47 | */ | ||
| 48 | public function validateAndComplete(array $mapping, ReflectionProperty $field): array | ||
| 49 | { | ||
| 50 | $type = $field->getType(); | ||
| 51 | |||
| 52 | if ( | ||
| 53 | ! isset($mapping['type']) | ||
| 54 | && ($type instanceof ReflectionNamedType) | ||
| 55 | ) { | ||
| 56 | if (! $type->isBuiltin() && enum_exists($type->getName())) { | ||
| 57 | $reflection = new ReflectionEnum($type->getName()); | ||
| 58 | if (! $reflection->isBacked()) { | ||
| 59 | throw MappingException::backedEnumTypeRequired( | ||
| 60 | $field->class, | ||
| 61 | $mapping['fieldName'], | ||
| 62 | $type->getName(), | ||
| 63 | ); | ||
| 64 | } | ||
| 65 | |||
| 66 | assert(is_a($type->getName(), BackedEnum::class, true)); | ||
| 67 | $mapping['enumType'] = $type->getName(); | ||
| 68 | $type = $reflection->getBackingType(); | ||
| 69 | |||
| 70 | assert($type instanceof ReflectionNamedType); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (isset($this->typedFieldMappings[$type->getName()])) { | ||
| 74 | $mapping['type'] = $this->typedFieldMappings[$type->getName()]; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | return $mapping; | ||
| 79 | } | ||
| 80 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | use BackedEnum; | ||
| 9 | |||
| 10 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 11 | final class DiscriminatorColumn implements MappingAttribute | ||
| 12 | { | ||
| 13 | public function __construct( | ||
| 14 | public readonly string|null $name = null, | ||
| 15 | public readonly string|null $type = null, | ||
| 16 | public readonly int|null $length = null, | ||
| 17 | public readonly string|null $columnDefinition = null, | ||
| 18 | /** @var class-string<BackedEnum>|null */ | ||
| 19 | public readonly string|null $enumType = null, | ||
| 20 | /** @var array<string, mixed> */ | ||
| 21 | public readonly array $options = [], | ||
| 22 | ) { | ||
| 23 | } | ||
| 24 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use ArrayAccess; | ||
| 8 | use BackedEnum; | ||
| 9 | use Exception; | ||
| 10 | |||
| 11 | use function in_array; | ||
| 12 | use function property_exists; | ||
| 13 | |||
| 14 | /** @template-implements ArrayAccess<string, mixed> */ | ||
| 15 | final class DiscriminatorColumnMapping implements ArrayAccess | ||
| 16 | { | ||
| 17 | use ArrayAccessImplementation; | ||
| 18 | |||
| 19 | /** The database length of the column. Optional. Default value taken from the type. */ | ||
| 20 | public int|null $length = null; | ||
| 21 | |||
| 22 | public string|null $columnDefinition = null; | ||
| 23 | |||
| 24 | /** @var class-string<BackedEnum>|null */ | ||
| 25 | public string|null $enumType = null; | ||
| 26 | |||
| 27 | /** @var array<string, mixed> */ | ||
| 28 | public array $options = []; | ||
| 29 | |||
| 30 | public function __construct( | ||
| 31 | public string $type, | ||
| 32 | public string $fieldName, | ||
| 33 | public string $name, | ||
| 34 | ) { | ||
| 35 | } | ||
| 36 | |||
| 37 | /** | ||
| 38 | * @psalm-param array{ | ||
| 39 | * type: string, | ||
| 40 | * fieldName: string, | ||
| 41 | * name: string, | ||
| 42 | * length?: int|null, | ||
| 43 | * columnDefinition?: string|null, | ||
| 44 | * enumType?: class-string<BackedEnum>|null, | ||
| 45 | * options?: array<string, mixed>|null, | ||
| 46 | * } $mappingArray | ||
| 47 | */ | ||
| 48 | public static function fromMappingArray(array $mappingArray): self | ||
| 49 | { | ||
| 50 | $mapping = new self( | ||
| 51 | $mappingArray['type'], | ||
| 52 | $mappingArray['fieldName'], | ||
| 53 | $mappingArray['name'], | ||
| 54 | ); | ||
| 55 | foreach ($mappingArray as $key => $value) { | ||
| 56 | if (in_array($key, ['type', 'fieldName', 'name'])) { | ||
| 57 | continue; | ||
| 58 | } | ||
| 59 | |||
| 60 | if (property_exists($mapping, $key)) { | ||
| 61 | $mapping->$key = $value ?? $mapping->$key; | ||
| 62 | } else { | ||
| 63 | throw new Exception('Unknown property ' . $key . ' on class ' . static::class); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | return $mapping; | ||
| 68 | } | ||
| 69 | |||
| 70 | /** @return list<string> */ | ||
| 71 | public function __sleep(): array | ||
| 72 | { | ||
| 73 | $serialized = ['type', 'fieldName', 'name']; | ||
| 74 | |||
| 75 | foreach (['length', 'columnDefinition', 'enumType', 'options'] as $stringOrArrayKey) { | ||
| 76 | if ($this->$stringOrArrayKey !== null) { | ||
| 77 | $serialized[] = $stringOrArrayKey; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | return $serialized; | ||
| 82 | } | ||
| 83 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 10 | final class DiscriminatorMap implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** @param array<int|string, string> $value */ | ||
| 13 | public function __construct( | ||
| 14 | public readonly array $value, | ||
| 15 | ) { | ||
| 16 | } | ||
| 17 | } | ||
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 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Events; | ||
| 8 | use Doctrine\ORM\Mapping; | ||
| 9 | use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; | ||
| 10 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 11 | use Doctrine\ORM\Mapping\MappingException; | ||
| 12 | use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; | ||
| 13 | use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; | ||
| 14 | use Doctrine\Persistence\Mapping\Driver\MappingDriver; | ||
| 15 | use InvalidArgumentException; | ||
| 16 | use ReflectionClass; | ||
| 17 | use ReflectionMethod; | ||
| 18 | use ReflectionProperty; | ||
| 19 | |||
| 20 | use function assert; | ||
| 21 | use function class_exists; | ||
| 22 | use function constant; | ||
| 23 | use function defined; | ||
| 24 | use function sprintf; | ||
| 25 | |||
| 26 | class AttributeDriver implements MappingDriver | ||
| 27 | { | ||
| 28 | use ColocatedMappingDriver; | ||
| 29 | use ReflectionBasedDriver; | ||
| 30 | |||
| 31 | private const ENTITY_ATTRIBUTE_CLASSES = [ | ||
| 32 | Mapping\Entity::class => 1, | ||
| 33 | Mapping\MappedSuperclass::class => 2, | ||
| 34 | ]; | ||
| 35 | |||
| 36 | private readonly AttributeReader $reader; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * @param array<string> $paths | ||
| 40 | * @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0 | ||
| 41 | */ | ||
| 42 | public function __construct(array $paths, bool $reportFieldsWhereDeclared = true) | ||
| 43 | { | ||
| 44 | if (! $reportFieldsWhereDeclared) { | ||
| 45 | throw new InvalidArgumentException(sprintf( | ||
| 46 | 'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.', | ||
| 47 | __METHOD__, | ||
| 48 | )); | ||
| 49 | } | ||
| 50 | |||
| 51 | $this->reader = new AttributeReader(); | ||
| 52 | $this->addPaths($paths); | ||
| 53 | } | ||
| 54 | |||
| 55 | public function isTransient(string $className): bool | ||
| 56 | { | ||
| 57 | $classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className)); | ||
| 58 | |||
| 59 | foreach ($classAttributes as $a) { | ||
| 60 | $attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a; | ||
| 61 | if (isset(self::ENTITY_ATTRIBUTE_CLASSES[$attr::class])) { | ||
| 62 | return false; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | return true; | ||
| 67 | } | ||
| 68 | |||
| 69 | /** | ||
| 70 | * {@inheritDoc} | ||
| 71 | * | ||
| 72 | * @psalm-param class-string<T> $className | ||
| 73 | * @psalm-param ClassMetadata<T> $metadata | ||
| 74 | * | ||
| 75 | * @template T of object | ||
| 76 | */ | ||
| 77 | public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void | ||
| 78 | { | ||
| 79 | $reflectionClass = $metadata->getReflectionClass() | ||
| 80 | // this happens when running attribute driver in combination with | ||
| 81 | // static reflection services. This is not the nicest fix | ||
| 82 | ?? new ReflectionClass($metadata->name); | ||
| 83 | |||
| 84 | $classAttributes = $this->reader->getClassAttributes($reflectionClass); | ||
| 85 | |||
| 86 | // Evaluate Entity attribute | ||
| 87 | if (isset($classAttributes[Mapping\Entity::class])) { | ||
| 88 | $entityAttribute = $classAttributes[Mapping\Entity::class]; | ||
| 89 | if ($entityAttribute->repositoryClass !== null) { | ||
| 90 | $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass); | ||
| 91 | } | ||
| 92 | |||
| 93 | if ($entityAttribute->readOnly) { | ||
| 94 | $metadata->markReadOnly(); | ||
| 95 | } | ||
| 96 | } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) { | ||
| 97 | $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class]; | ||
| 98 | |||
| 99 | $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass); | ||
| 100 | $metadata->isMappedSuperclass = true; | ||
| 101 | } elseif (isset($classAttributes[Mapping\Embeddable::class])) { | ||
| 102 | $metadata->isEmbeddedClass = true; | ||
| 103 | } else { | ||
| 104 | throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); | ||
| 105 | } | ||
| 106 | |||
| 107 | $primaryTable = []; | ||
| 108 | |||
| 109 | if (isset($classAttributes[Mapping\Table::class])) { | ||
| 110 | $tableAnnot = $classAttributes[Mapping\Table::class]; | ||
| 111 | $primaryTable['name'] = $tableAnnot->name; | ||
| 112 | $primaryTable['schema'] = $tableAnnot->schema; | ||
| 113 | |||
| 114 | if ($tableAnnot->options) { | ||
| 115 | $primaryTable['options'] = $tableAnnot->options; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | if (isset($classAttributes[Mapping\Index::class])) { | ||
| 120 | if ($metadata->isEmbeddedClass) { | ||
| 121 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Index::class); | ||
| 122 | } | ||
| 123 | |||
| 124 | foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) { | ||
| 125 | $index = []; | ||
| 126 | |||
| 127 | if (! empty($indexAnnot->columns)) { | ||
| 128 | $index['columns'] = $indexAnnot->columns; | ||
| 129 | } | ||
| 130 | |||
| 131 | if (! empty($indexAnnot->fields)) { | ||
| 132 | $index['fields'] = $indexAnnot->fields; | ||
| 133 | } | ||
| 134 | |||
| 135 | if ( | ||
| 136 | isset($index['columns'], $index['fields']) | ||
| 137 | || ( | ||
| 138 | ! isset($index['columns']) | ||
| 139 | && ! isset($index['fields']) | ||
| 140 | ) | ||
| 141 | ) { | ||
| 142 | throw MappingException::invalidIndexConfiguration( | ||
| 143 | $className, | ||
| 144 | (string) ($indexAnnot->name ?? $idx), | ||
| 145 | ); | ||
| 146 | } | ||
| 147 | |||
| 148 | if (! empty($indexAnnot->flags)) { | ||
| 149 | $index['flags'] = $indexAnnot->flags; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (! empty($indexAnnot->options)) { | ||
| 153 | $index['options'] = $indexAnnot->options; | ||
| 154 | } | ||
| 155 | |||
| 156 | if (! empty($indexAnnot->name)) { | ||
| 157 | $primaryTable['indexes'][$indexAnnot->name] = $index; | ||
| 158 | } else { | ||
| 159 | $primaryTable['indexes'][] = $index; | ||
| 160 | } | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | if (isset($classAttributes[Mapping\UniqueConstraint::class])) { | ||
| 165 | if ($metadata->isEmbeddedClass) { | ||
| 166 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\UniqueConstraint::class); | ||
| 167 | } | ||
| 168 | |||
| 169 | foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) { | ||
| 170 | $uniqueConstraint = []; | ||
| 171 | |||
| 172 | if (! empty($uniqueConstraintAnnot->columns)) { | ||
| 173 | $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns; | ||
| 174 | } | ||
| 175 | |||
| 176 | if (! empty($uniqueConstraintAnnot->fields)) { | ||
| 177 | $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields; | ||
| 178 | } | ||
| 179 | |||
| 180 | if ( | ||
| 181 | isset($uniqueConstraint['columns'], $uniqueConstraint['fields']) | ||
| 182 | || ( | ||
| 183 | ! isset($uniqueConstraint['columns']) | ||
| 184 | && ! isset($uniqueConstraint['fields']) | ||
| 185 | ) | ||
| 186 | ) { | ||
| 187 | throw MappingException::invalidUniqueConstraintConfiguration( | ||
| 188 | $className, | ||
| 189 | (string) ($uniqueConstraintAnnot->name ?? $idx), | ||
| 190 | ); | ||
| 191 | } | ||
| 192 | |||
| 193 | if (! empty($uniqueConstraintAnnot->options)) { | ||
| 194 | $uniqueConstraint['options'] = $uniqueConstraintAnnot->options; | ||
| 195 | } | ||
| 196 | |||
| 197 | if (! empty($uniqueConstraintAnnot->name)) { | ||
| 198 | $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; | ||
| 199 | } else { | ||
| 200 | $primaryTable['uniqueConstraints'][] = $uniqueConstraint; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | $metadata->setPrimaryTable($primaryTable); | ||
| 206 | |||
| 207 | // Evaluate #[Cache] attribute | ||
| 208 | if (isset($classAttributes[Mapping\Cache::class])) { | ||
| 209 | if ($metadata->isEmbeddedClass) { | ||
| 210 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Cache::class); | ||
| 211 | } | ||
| 212 | |||
| 213 | $cacheAttribute = $classAttributes[Mapping\Cache::class]; | ||
| 214 | $cacheMap = [ | ||
| 215 | 'region' => $cacheAttribute->region, | ||
| 216 | 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), | ||
| 217 | ]; | ||
| 218 | |||
| 219 | $metadata->enableCache($cacheMap); | ||
| 220 | } | ||
| 221 | |||
| 222 | // Evaluate InheritanceType attribute | ||
| 223 | if (isset($classAttributes[Mapping\InheritanceType::class])) { | ||
| 224 | if ($metadata->isEmbeddedClass) { | ||
| 225 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\InheritanceType::class); | ||
| 226 | } | ||
| 227 | |||
| 228 | $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class]; | ||
| 229 | |||
| 230 | $metadata->setInheritanceType( | ||
| 231 | constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value), | ||
| 232 | ); | ||
| 233 | |||
| 234 | if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { | ||
| 235 | // Evaluate DiscriminatorColumn attribute | ||
| 236 | if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) { | ||
| 237 | $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class]; | ||
| 238 | assert($discrColumnAttribute instanceof Mapping\DiscriminatorColumn); | ||
| 239 | |||
| 240 | $columnDef = [ | ||
| 241 | 'name' => $discrColumnAttribute->name, | ||
| 242 | 'type' => $discrColumnAttribute->type ?? 'string', | ||
| 243 | 'length' => $discrColumnAttribute->length ?? 255, | ||
| 244 | 'columnDefinition' => $discrColumnAttribute->columnDefinition, | ||
| 245 | 'enumType' => $discrColumnAttribute->enumType, | ||
| 246 | ]; | ||
| 247 | |||
| 248 | if ($discrColumnAttribute->options) { | ||
| 249 | $columnDef['options'] = $discrColumnAttribute->options; | ||
| 250 | } | ||
| 251 | |||
| 252 | $metadata->setDiscriminatorColumn($columnDef); | ||
| 253 | } else { | ||
| 254 | $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); | ||
| 255 | } | ||
| 256 | |||
| 257 | // Evaluate DiscriminatorMap attribute | ||
| 258 | if (isset($classAttributes[Mapping\DiscriminatorMap::class])) { | ||
| 259 | $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class]; | ||
| 260 | $metadata->setDiscriminatorMap($discrMapAttribute->value); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | // Evaluate DoctrineChangeTrackingPolicy attribute | ||
| 266 | if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) { | ||
| 267 | if ($metadata->isEmbeddedClass) { | ||
| 268 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ChangeTrackingPolicy::class); | ||
| 269 | } | ||
| 270 | |||
| 271 | $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class]; | ||
| 272 | $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value)); | ||
| 273 | } | ||
| 274 | |||
| 275 | foreach ($reflectionClass->getProperties() as $property) { | ||
| 276 | assert($property instanceof ReflectionProperty); | ||
| 277 | |||
| 278 | if ($this->isRepeatedPropertyDeclaration($property, $metadata)) { | ||
| 279 | continue; | ||
| 280 | } | ||
| 281 | |||
| 282 | $mapping = []; | ||
| 283 | $mapping['fieldName'] = $property->name; | ||
| 284 | |||
| 285 | // Evaluate #[Cache] attribute | ||
| 286 | $cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class); | ||
| 287 | if ($cacheAttribute !== null) { | ||
| 288 | assert($cacheAttribute instanceof Mapping\Cache); | ||
| 289 | |||
| 290 | $mapping['cache'] = $metadata->getAssociationCacheDefaults( | ||
| 291 | $mapping['fieldName'], | ||
| 292 | [ | ||
| 293 | 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), | ||
| 294 | 'region' => $cacheAttribute->region, | ||
| 295 | ], | ||
| 296 | ); | ||
| 297 | } | ||
| 298 | |||
| 299 | // Check for JoinColumn/JoinColumns attributes | ||
| 300 | $joinColumns = []; | ||
| 301 | |||
| 302 | $joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class); | ||
| 303 | |||
| 304 | foreach ($joinColumnAttributes as $joinColumnAttribute) { | ||
| 305 | $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute); | ||
| 306 | } | ||
| 307 | |||
| 308 | // Field can only be attributed with one of: | ||
| 309 | // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded | ||
| 310 | $columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class); | ||
| 311 | $oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class); | ||
| 312 | $oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class); | ||
| 313 | $manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class); | ||
| 314 | $manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class); | ||
| 315 | $embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class); | ||
| 316 | |||
| 317 | if ($columnAttribute !== null) { | ||
| 318 | $mapping = $this->columnToArray($property->name, $columnAttribute); | ||
| 319 | |||
| 320 | if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { | ||
| 321 | $mapping['id'] = true; | ||
| 322 | } | ||
| 323 | |||
| 324 | $generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class); | ||
| 325 | |||
| 326 | if ($generatedValueAttribute !== null) { | ||
| 327 | $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy)); | ||
| 328 | } | ||
| 329 | |||
| 330 | if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) { | ||
| 331 | $metadata->setVersionMapping($mapping); | ||
| 332 | } | ||
| 333 | |||
| 334 | $metadata->mapField($mapping); | ||
| 335 | |||
| 336 | // Check for SequenceGenerator/TableGenerator definition | ||
| 337 | $seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class); | ||
| 338 | $customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class); | ||
| 339 | |||
| 340 | if ($seqGeneratorAttribute !== null) { | ||
| 341 | $metadata->setSequenceGeneratorDefinition( | ||
| 342 | [ | ||
| 343 | 'sequenceName' => $seqGeneratorAttribute->sequenceName, | ||
| 344 | 'allocationSize' => $seqGeneratorAttribute->allocationSize, | ||
| 345 | 'initialValue' => $seqGeneratorAttribute->initialValue, | ||
| 346 | ], | ||
| 347 | ); | ||
| 348 | } elseif ($customGeneratorAttribute !== null) { | ||
| 349 | $metadata->setCustomGeneratorDefinition( | ||
| 350 | [ | ||
| 351 | 'class' => $customGeneratorAttribute->class, | ||
| 352 | ], | ||
| 353 | ); | ||
| 354 | } | ||
| 355 | } elseif ($oneToOneAttribute !== null) { | ||
| 356 | if ($metadata->isEmbeddedClass) { | ||
| 357 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToOne::class); | ||
| 358 | } | ||
| 359 | |||
| 360 | if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { | ||
| 361 | $mapping['id'] = true; | ||
| 362 | } | ||
| 363 | |||
| 364 | $mapping['targetEntity'] = $oneToOneAttribute->targetEntity; | ||
| 365 | $mapping['joinColumns'] = $joinColumns; | ||
| 366 | $mapping['mappedBy'] = $oneToOneAttribute->mappedBy; | ||
| 367 | $mapping['inversedBy'] = $oneToOneAttribute->inversedBy; | ||
| 368 | $mapping['cascade'] = $oneToOneAttribute->cascade; | ||
| 369 | $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval; | ||
| 370 | $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch); | ||
| 371 | $metadata->mapOneToOne($mapping); | ||
| 372 | } elseif ($oneToManyAttribute !== null) { | ||
| 373 | if ($metadata->isEmbeddedClass) { | ||
| 374 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class); | ||
| 375 | } | ||
| 376 | |||
| 377 | $mapping['mappedBy'] = $oneToManyAttribute->mappedBy; | ||
| 378 | $mapping['targetEntity'] = $oneToManyAttribute->targetEntity; | ||
| 379 | $mapping['cascade'] = $oneToManyAttribute->cascade; | ||
| 380 | $mapping['indexBy'] = $oneToManyAttribute->indexBy; | ||
| 381 | $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval; | ||
| 382 | $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch); | ||
| 383 | |||
| 384 | $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); | ||
| 385 | |||
| 386 | if ($orderByAttribute !== null) { | ||
| 387 | $mapping['orderBy'] = $orderByAttribute->value; | ||
| 388 | } | ||
| 389 | |||
| 390 | $metadata->mapOneToMany($mapping); | ||
| 391 | } elseif ($manyToOneAttribute !== null) { | ||
| 392 | if ($metadata->isEmbeddedClass) { | ||
| 393 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class); | ||
| 394 | } | ||
| 395 | |||
| 396 | $idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class); | ||
| 397 | |||
| 398 | if ($idAttribute !== null) { | ||
| 399 | $mapping['id'] = true; | ||
| 400 | } | ||
| 401 | |||
| 402 | $mapping['joinColumns'] = $joinColumns; | ||
| 403 | $mapping['cascade'] = $manyToOneAttribute->cascade; | ||
| 404 | $mapping['inversedBy'] = $manyToOneAttribute->inversedBy; | ||
| 405 | $mapping['targetEntity'] = $manyToOneAttribute->targetEntity; | ||
| 406 | $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch); | ||
| 407 | $metadata->mapManyToOne($mapping); | ||
| 408 | } elseif ($manyToManyAttribute !== null) { | ||
| 409 | if ($metadata->isEmbeddedClass) { | ||
| 410 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToMany::class); | ||
| 411 | } | ||
| 412 | |||
| 413 | $joinTable = []; | ||
| 414 | $joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class); | ||
| 415 | |||
| 416 | if ($joinTableAttribute !== null) { | ||
| 417 | $joinTable = [ | ||
| 418 | 'name' => $joinTableAttribute->name, | ||
| 419 | 'schema' => $joinTableAttribute->schema, | ||
| 420 | ]; | ||
| 421 | |||
| 422 | if ($joinTableAttribute->options) { | ||
| 423 | $joinTable['options'] = $joinTableAttribute->options; | ||
| 424 | } | ||
| 425 | |||
| 426 | foreach ($joinTableAttribute->joinColumns as $joinColumn) { | ||
| 427 | $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); | ||
| 428 | } | ||
| 429 | |||
| 430 | foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) { | ||
| 431 | $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) { | ||
| 436 | $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); | ||
| 437 | } | ||
| 438 | |||
| 439 | foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) { | ||
| 440 | $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); | ||
| 441 | } | ||
| 442 | |||
| 443 | $mapping['joinTable'] = $joinTable; | ||
| 444 | $mapping['targetEntity'] = $manyToManyAttribute->targetEntity; | ||
| 445 | $mapping['mappedBy'] = $manyToManyAttribute->mappedBy; | ||
| 446 | $mapping['inversedBy'] = $manyToManyAttribute->inversedBy; | ||
| 447 | $mapping['cascade'] = $manyToManyAttribute->cascade; | ||
| 448 | $mapping['indexBy'] = $manyToManyAttribute->indexBy; | ||
| 449 | $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval; | ||
| 450 | $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch); | ||
| 451 | |||
| 452 | $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); | ||
| 453 | |||
| 454 | if ($orderByAttribute !== null) { | ||
| 455 | $mapping['orderBy'] = $orderByAttribute->value; | ||
| 456 | } | ||
| 457 | |||
| 458 | $metadata->mapManyToMany($mapping); | ||
| 459 | } elseif ($embeddedAttribute !== null) { | ||
| 460 | $mapping['class'] = $embeddedAttribute->class; | ||
| 461 | $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix; | ||
| 462 | |||
| 463 | $metadata->mapEmbedded($mapping); | ||
| 464 | } | ||
| 465 | } | ||
| 466 | |||
| 467 | // Evaluate AssociationOverrides attribute | ||
| 468 | if (isset($classAttributes[Mapping\AssociationOverrides::class])) { | ||
| 469 | if ($metadata->isEmbeddedClass) { | ||
| 470 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AssociationOverride::class); | ||
| 471 | } | ||
| 472 | |||
| 473 | $associationOverride = $classAttributes[Mapping\AssociationOverrides::class]; | ||
| 474 | |||
| 475 | foreach ($associationOverride->overrides as $associationOverride) { | ||
| 476 | $override = []; | ||
| 477 | $fieldName = $associationOverride->name; | ||
| 478 | |||
| 479 | // Check for JoinColumn/JoinColumns attributes | ||
| 480 | if ($associationOverride->joinColumns) { | ||
| 481 | $joinColumns = []; | ||
| 482 | |||
| 483 | foreach ($associationOverride->joinColumns as $joinColumn) { | ||
| 484 | $joinColumns[] = $this->joinColumnToArray($joinColumn); | ||
| 485 | } | ||
| 486 | |||
| 487 | $override['joinColumns'] = $joinColumns; | ||
| 488 | } | ||
| 489 | |||
| 490 | if ($associationOverride->inverseJoinColumns) { | ||
| 491 | $joinColumns = []; | ||
| 492 | |||
| 493 | foreach ($associationOverride->inverseJoinColumns as $joinColumn) { | ||
| 494 | $joinColumns[] = $this->joinColumnToArray($joinColumn); | ||
| 495 | } | ||
| 496 | |||
| 497 | $override['inverseJoinColumns'] = $joinColumns; | ||
| 498 | } | ||
| 499 | |||
| 500 | // Check for JoinTable attributes | ||
| 501 | if ($associationOverride->joinTable) { | ||
| 502 | $joinTableAnnot = $associationOverride->joinTable; | ||
| 503 | $joinTable = [ | ||
| 504 | 'name' => $joinTableAnnot->name, | ||
| 505 | 'schema' => $joinTableAnnot->schema, | ||
| 506 | 'joinColumns' => $override['joinColumns'] ?? [], | ||
| 507 | 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [], | ||
| 508 | ]; | ||
| 509 | |||
| 510 | unset($override['joinColumns'], $override['inverseJoinColumns']); | ||
| 511 | |||
| 512 | $override['joinTable'] = $joinTable; | ||
| 513 | } | ||
| 514 | |||
| 515 | // Check for inversedBy | ||
| 516 | if ($associationOverride->inversedBy) { | ||
| 517 | $override['inversedBy'] = $associationOverride->inversedBy; | ||
| 518 | } | ||
| 519 | |||
| 520 | // Check for `fetch` | ||
| 521 | if ($associationOverride->fetch) { | ||
| 522 | $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch); | ||
| 523 | } | ||
| 524 | |||
| 525 | $metadata->setAssociationOverride($fieldName, $override); | ||
| 526 | } | ||
| 527 | } | ||
| 528 | |||
| 529 | // Evaluate AttributeOverrides attribute | ||
| 530 | if (isset($classAttributes[Mapping\AttributeOverrides::class])) { | ||
| 531 | if ($metadata->isEmbeddedClass) { | ||
| 532 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AttributeOverrides::class); | ||
| 533 | } | ||
| 534 | |||
| 535 | $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class]; | ||
| 536 | |||
| 537 | foreach ($attributeOverridesAnnot->overrides as $attributeOverride) { | ||
| 538 | $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column); | ||
| 539 | |||
| 540 | $metadata->setAttributeOverride($attributeOverride->name, $mapping); | ||
| 541 | } | ||
| 542 | } | ||
| 543 | |||
| 544 | // Evaluate EntityListeners attribute | ||
| 545 | if (isset($classAttributes[Mapping\EntityListeners::class])) { | ||
| 546 | if ($metadata->isEmbeddedClass) { | ||
| 547 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\EntityListeners::class); | ||
| 548 | } | ||
| 549 | |||
| 550 | $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class]; | ||
| 551 | |||
| 552 | foreach ($entityListenersAttribute->value as $item) { | ||
| 553 | $listenerClassName = $metadata->fullyQualifiedClassName($item); | ||
| 554 | |||
| 555 | if (! class_exists($listenerClassName)) { | ||
| 556 | throw MappingException::entityListenerClassNotFound($listenerClassName, $className); | ||
| 557 | } | ||
| 558 | |||
| 559 | $hasMapping = false; | ||
| 560 | $listenerClass = new ReflectionClass($listenerClassName); | ||
| 561 | |||
| 562 | foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { | ||
| 563 | assert($method instanceof ReflectionMethod); | ||
| 564 | // find method callbacks. | ||
| 565 | $callbacks = $this->getMethodCallbacks($method); | ||
| 566 | $hasMapping = $hasMapping ?: ! empty($callbacks); | ||
| 567 | |||
| 568 | foreach ($callbacks as $value) { | ||
| 569 | $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); | ||
| 570 | } | ||
| 571 | } | ||
| 572 | |||
| 573 | // Evaluate the listener using naming convention. | ||
| 574 | if (! $hasMapping) { | ||
| 575 | EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); | ||
| 576 | } | ||
| 577 | } | ||
| 578 | } | ||
| 579 | |||
| 580 | // Evaluate #[HasLifecycleCallbacks] attribute | ||
| 581 | if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) { | ||
| 582 | if ($metadata->isEmbeddedClass) { | ||
| 583 | throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\HasLifecycleCallbacks::class); | ||
| 584 | } | ||
| 585 | |||
| 586 | foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { | ||
| 587 | assert($method instanceof ReflectionMethod); | ||
| 588 | foreach ($this->getMethodCallbacks($method) as $value) { | ||
| 589 | $metadata->addLifecycleCallback($value[0], $value[1]); | ||
| 590 | } | ||
| 591 | } | ||
| 592 | } | ||
| 593 | } | ||
| 594 | |||
| 595 | /** | ||
| 596 | * Attempts to resolve the fetch mode. | ||
| 597 | * | ||
| 598 | * @param class-string $className The class name. | ||
| 599 | * @param string $fetchMode The fetch mode. | ||
| 600 | * | ||
| 601 | * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata. | ||
| 602 | * | ||
| 603 | * @throws MappingException If the fetch mode is not valid. | ||
| 604 | */ | ||
| 605 | private function getFetchMode(string $className, string $fetchMode): int | ||
| 606 | { | ||
| 607 | if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { | ||
| 608 | throw MappingException::invalidFetchMode($className, $fetchMode); | ||
| 609 | } | ||
| 610 | |||
| 611 | return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); | ||
| 612 | } | ||
| 613 | |||
| 614 | /** | ||
| 615 | * Attempts to resolve the generated mode. | ||
| 616 | * | ||
| 617 | * @throws MappingException If the fetch mode is not valid. | ||
| 618 | */ | ||
| 619 | private function getGeneratedMode(string $generatedMode): int | ||
| 620 | { | ||
| 621 | if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) { | ||
| 622 | throw MappingException::invalidGeneratedMode($generatedMode); | ||
| 623 | } | ||
| 624 | |||
| 625 | return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode); | ||
| 626 | } | ||
| 627 | |||
| 628 | /** | ||
| 629 | * Parses the given method. | ||
| 630 | * | ||
| 631 | * @return list<array{string, string}> | ||
| 632 | * @psalm-return list<array{string, (Events::*)}> | ||
| 633 | */ | ||
| 634 | private function getMethodCallbacks(ReflectionMethod $method): array | ||
| 635 | { | ||
| 636 | $callbacks = []; | ||
| 637 | $attributes = $this->reader->getMethodAttributes($method); | ||
| 638 | |||
| 639 | foreach ($attributes as $attribute) { | ||
| 640 | if ($attribute instanceof Mapping\PrePersist) { | ||
| 641 | $callbacks[] = [$method->name, Events::prePersist]; | ||
| 642 | } | ||
| 643 | |||
| 644 | if ($attribute instanceof Mapping\PostPersist) { | ||
| 645 | $callbacks[] = [$method->name, Events::postPersist]; | ||
| 646 | } | ||
| 647 | |||
| 648 | if ($attribute instanceof Mapping\PreUpdate) { | ||
| 649 | $callbacks[] = [$method->name, Events::preUpdate]; | ||
| 650 | } | ||
| 651 | |||
| 652 | if ($attribute instanceof Mapping\PostUpdate) { | ||
| 653 | $callbacks[] = [$method->name, Events::postUpdate]; | ||
| 654 | } | ||
| 655 | |||
| 656 | if ($attribute instanceof Mapping\PreRemove) { | ||
| 657 | $callbacks[] = [$method->name, Events::preRemove]; | ||
| 658 | } | ||
| 659 | |||
| 660 | if ($attribute instanceof Mapping\PostRemove) { | ||
| 661 | $callbacks[] = [$method->name, Events::postRemove]; | ||
| 662 | } | ||
| 663 | |||
| 664 | if ($attribute instanceof Mapping\PostLoad) { | ||
| 665 | $callbacks[] = [$method->name, Events::postLoad]; | ||
| 666 | } | ||
| 667 | |||
| 668 | if ($attribute instanceof Mapping\PreFlush) { | ||
| 669 | $callbacks[] = [$method->name, Events::preFlush]; | ||
| 670 | } | ||
| 671 | } | ||
| 672 | |||
| 673 | return $callbacks; | ||
| 674 | } | ||
| 675 | |||
| 676 | /** | ||
| 677 | * Parse the given JoinColumn as array | ||
| 678 | * | ||
| 679 | * @return mixed[] | ||
| 680 | * @psalm-return array{ | ||
| 681 | * name: string|null, | ||
| 682 | * unique: bool, | ||
| 683 | * nullable: bool, | ||
| 684 | * onDelete: mixed, | ||
| 685 | * columnDefinition: string|null, | ||
| 686 | * referencedColumnName: string, | ||
| 687 | * options?: array<string, mixed> | ||
| 688 | * } | ||
| 689 | */ | ||
| 690 | private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn): array | ||
| 691 | { | ||
| 692 | $mapping = [ | ||
| 693 | 'name' => $joinColumn->name, | ||
| 694 | 'unique' => $joinColumn->unique, | ||
| 695 | 'nullable' => $joinColumn->nullable, | ||
| 696 | 'onDelete' => $joinColumn->onDelete, | ||
| 697 | 'columnDefinition' => $joinColumn->columnDefinition, | ||
| 698 | 'referencedColumnName' => $joinColumn->referencedColumnName, | ||
| 699 | ]; | ||
| 700 | |||
| 701 | if ($joinColumn->options) { | ||
| 702 | $mapping['options'] = $joinColumn->options; | ||
| 703 | } | ||
| 704 | |||
| 705 | return $mapping; | ||
| 706 | } | ||
| 707 | |||
| 708 | /** | ||
| 709 | * Parse the given Column as array | ||
| 710 | * | ||
| 711 | * @return mixed[] | ||
| 712 | * @psalm-return array{ | ||
| 713 | * fieldName: string, | ||
| 714 | * type: mixed, | ||
| 715 | * scale: int, | ||
| 716 | * length: int, | ||
| 717 | * unique: bool, | ||
| 718 | * nullable: bool, | ||
| 719 | * precision: int, | ||
| 720 | * enumType?: class-string, | ||
| 721 | * options?: mixed[], | ||
| 722 | * columnName?: string, | ||
| 723 | * columnDefinition?: string | ||
| 724 | * } | ||
| 725 | */ | ||
| 726 | private function columnToArray(string $fieldName, Mapping\Column $column): array | ||
| 727 | { | ||
| 728 | $mapping = [ | ||
| 729 | 'fieldName' => $fieldName, | ||
| 730 | 'type' => $column->type, | ||
| 731 | 'scale' => $column->scale, | ||
| 732 | 'length' => $column->length, | ||
| 733 | 'unique' => $column->unique, | ||
| 734 | 'nullable' => $column->nullable, | ||
| 735 | 'precision' => $column->precision, | ||
| 736 | ]; | ||
| 737 | |||
| 738 | if ($column->options) { | ||
| 739 | $mapping['options'] = $column->options; | ||
| 740 | } | ||
| 741 | |||
| 742 | if (isset($column->name)) { | ||
| 743 | $mapping['columnName'] = $column->name; | ||
| 744 | } | ||
| 745 | |||
| 746 | if (isset($column->columnDefinition)) { | ||
| 747 | $mapping['columnDefinition'] = $column->columnDefinition; | ||
| 748 | } | ||
| 749 | |||
| 750 | if ($column->updatable === false) { | ||
| 751 | $mapping['notUpdatable'] = true; | ||
| 752 | } | ||
| 753 | |||
| 754 | if ($column->insertable === false) { | ||
| 755 | $mapping['notInsertable'] = true; | ||
| 756 | } | ||
| 757 | |||
| 758 | if ($column->generated !== null) { | ||
| 759 | $mapping['generated'] = $this->getGeneratedMode($column->generated); | ||
| 760 | } | ||
| 761 | |||
| 762 | if ($column->enumType) { | ||
| 763 | $mapping['enumType'] = $column->enumType; | ||
| 764 | } | ||
| 765 | |||
| 766 | return $mapping; | ||
| 767 | } | ||
| 768 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | use Doctrine\ORM\Mapping\MappingAttribute; | ||
| 9 | use LogicException; | ||
| 10 | use ReflectionAttribute; | ||
| 11 | use ReflectionClass; | ||
| 12 | use ReflectionMethod; | ||
| 13 | use ReflectionProperty; | ||
| 14 | |||
| 15 | use function assert; | ||
| 16 | use function is_string; | ||
| 17 | use function is_subclass_of; | ||
| 18 | use function sprintf; | ||
| 19 | |||
| 20 | /** @internal */ | ||
| 21 | final class AttributeReader | ||
| 22 | { | ||
| 23 | /** @var array<class-string<MappingAttribute>, bool> */ | ||
| 24 | private array $isRepeatableAttribute = []; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * @psalm-return class-string-map<T, T|RepeatableAttributeCollection<T>> | ||
| 28 | * | ||
| 29 | * @template T of MappingAttribute | ||
| 30 | */ | ||
| 31 | public function getClassAttributes(ReflectionClass $class): array | ||
| 32 | { | ||
| 33 | return $this->convertToAttributeInstances($class->getAttributes()); | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * @return class-string-map<T, T|RepeatableAttributeCollection<T>> | ||
| 38 | * | ||
| 39 | * @template T of MappingAttribute | ||
| 40 | */ | ||
| 41 | public function getMethodAttributes(ReflectionMethod $method): array | ||
| 42 | { | ||
| 43 | return $this->convertToAttributeInstances($method->getAttributes()); | ||
| 44 | } | ||
| 45 | |||
| 46 | /** | ||
| 47 | * @return class-string-map<T, T|RepeatableAttributeCollection<T>> | ||
| 48 | * | ||
| 49 | * @template T of MappingAttribute | ||
| 50 | */ | ||
| 51 | public function getPropertyAttributes(ReflectionProperty $property): array | ||
| 52 | { | ||
| 53 | return $this->convertToAttributeInstances($property->getAttributes()); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * @param class-string<T> $attributeName The name of the annotation. | ||
| 58 | * | ||
| 59 | * @return T|null | ||
| 60 | * | ||
| 61 | * @template T of MappingAttribute | ||
| 62 | */ | ||
| 63 | public function getPropertyAttribute(ReflectionProperty $property, string $attributeName) | ||
| 64 | { | ||
| 65 | if ($this->isRepeatable($attributeName)) { | ||
| 66 | throw new LogicException(sprintf( | ||
| 67 | 'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.', | ||
| 68 | $attributeName, | ||
| 69 | )); | ||
| 70 | } | ||
| 71 | |||
| 72 | return $this->getPropertyAttributes($property)[$attributeName] ?? null; | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 76 | * @param class-string<T> $attributeName The name of the annotation. | ||
| 77 | * | ||
| 78 | * @return RepeatableAttributeCollection<T> | ||
| 79 | * | ||
| 80 | * @template T of MappingAttribute | ||
| 81 | */ | ||
| 82 | public function getPropertyAttributeCollection( | ||
| 83 | ReflectionProperty $property, | ||
| 84 | string $attributeName, | ||
| 85 | ): RepeatableAttributeCollection { | ||
| 86 | if (! $this->isRepeatable($attributeName)) { | ||
| 87 | throw new LogicException(sprintf( | ||
| 88 | 'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.', | ||
| 89 | $attributeName, | ||
| 90 | )); | ||
| 91 | } | ||
| 92 | |||
| 93 | return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection(); | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * @param array<ReflectionAttribute> $attributes | ||
| 98 | * | ||
| 99 | * @return class-string-map<T, T|RepeatableAttributeCollection<T>> | ||
| 100 | * | ||
| 101 | * @template T of MappingAttribute | ||
| 102 | */ | ||
| 103 | private function convertToAttributeInstances(array $attributes): array | ||
| 104 | { | ||
| 105 | $instances = []; | ||
| 106 | |||
| 107 | foreach ($attributes as $attribute) { | ||
| 108 | $attributeName = $attribute->getName(); | ||
| 109 | assert(is_string($attributeName)); | ||
| 110 | // Make sure we only get Doctrine Attributes | ||
| 111 | if (! is_subclass_of($attributeName, MappingAttribute::class)) { | ||
| 112 | continue; | ||
| 113 | } | ||
| 114 | |||
| 115 | $instance = $attribute->newInstance(); | ||
| 116 | assert($instance instanceof MappingAttribute); | ||
| 117 | |||
| 118 | if ($this->isRepeatable($attributeName)) { | ||
| 119 | if (! isset($instances[$attributeName])) { | ||
| 120 | $instances[$attributeName] = new RepeatableAttributeCollection(); | ||
| 121 | } | ||
| 122 | |||
| 123 | $collection = $instances[$attributeName]; | ||
| 124 | assert($collection instanceof RepeatableAttributeCollection); | ||
| 125 | $collection[] = $instance; | ||
| 126 | } else { | ||
| 127 | $instances[$attributeName] = $instance; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | return $instances; | ||
| 132 | } | ||
| 133 | |||
| 134 | /** @param class-string<MappingAttribute> $attributeClassName */ | ||
| 135 | private function isRepeatable(string $attributeClassName): bool | ||
| 136 | { | ||
| 137 | if (isset($this->isRepeatableAttribute[$attributeClassName])) { | ||
| 138 | return $this->isRepeatableAttribute[$attributeClassName]; | ||
| 139 | } | ||
| 140 | |||
| 141 | $reflectionClass = new ReflectionClass($attributeClassName); | ||
| 142 | $attribute = $reflectionClass->getAttributes()[0]->newInstance(); | ||
| 143 | |||
| 144 | return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0; | ||
| 145 | } | ||
| 146 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Schema\AbstractSchemaManager; | ||
| 8 | use Doctrine\DBAL\Schema\Column; | ||
| 9 | use Doctrine\DBAL\Schema\SchemaException; | ||
| 10 | use Doctrine\DBAL\Schema\Table; | ||
| 11 | use Doctrine\DBAL\Types\Type; | ||
| 12 | use Doctrine\DBAL\Types\Types; | ||
| 13 | use Doctrine\Inflector\Inflector; | ||
| 14 | use Doctrine\Inflector\InflectorFactory; | ||
| 15 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 16 | use Doctrine\ORM\Mapping\MappingException; | ||
| 17 | use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; | ||
| 18 | use Doctrine\Persistence\Mapping\Driver\MappingDriver; | ||
| 19 | use InvalidArgumentException; | ||
| 20 | use TypeError; | ||
| 21 | |||
| 22 | use function array_diff; | ||
| 23 | use function array_keys; | ||
| 24 | use function array_merge; | ||
| 25 | use function assert; | ||
| 26 | use function count; | ||
| 27 | use function current; | ||
| 28 | use function get_debug_type; | ||
| 29 | use function in_array; | ||
| 30 | use function preg_replace; | ||
| 31 | use function sort; | ||
| 32 | use function sprintf; | ||
| 33 | use function strtolower; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * The DatabaseDriver reverse engineers the mapping metadata from a database. | ||
| 37 | * | ||
| 38 | * @link www.doctrine-project.org | ||
| 39 | */ | ||
| 40 | class DatabaseDriver implements MappingDriver | ||
| 41 | { | ||
| 42 | /** | ||
| 43 | * Replacement for {@see Types::ARRAY}. | ||
| 44 | * | ||
| 45 | * To be removed as soon as support for DBAL 3 is dropped. | ||
| 46 | */ | ||
| 47 | private const ARRAY = 'array'; | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Replacement for {@see Types::OBJECT}. | ||
| 51 | * | ||
| 52 | * To be removed as soon as support for DBAL 3 is dropped. | ||
| 53 | */ | ||
| 54 | private const OBJECT = 'object'; | ||
| 55 | |||
| 56 | /** @var array<string,Table>|null */ | ||
| 57 | private array|null $tables = null; | ||
| 58 | |||
| 59 | /** @var array<class-string, string> */ | ||
| 60 | private array $classToTableNames = []; | ||
| 61 | |||
| 62 | /** @psalm-var array<string, Table> */ | ||
| 63 | private array $manyToManyTables = []; | ||
| 64 | |||
| 65 | /** @var mixed[] */ | ||
| 66 | private array $classNamesForTables = []; | ||
| 67 | |||
| 68 | /** @var mixed[] */ | ||
| 69 | private array $fieldNamesForColumns = []; | ||
| 70 | |||
| 71 | /** | ||
| 72 | * The namespace for the generated entities. | ||
| 73 | */ | ||
| 74 | private string|null $namespace = null; | ||
| 75 | |||
| 76 | private Inflector $inflector; | ||
| 77 | |||
| 78 | public function __construct(private readonly AbstractSchemaManager $sm) | ||
| 79 | { | ||
| 80 | $this->inflector = InflectorFactory::create()->build(); | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Set the namespace for the generated entities. | ||
| 85 | */ | ||
| 86 | public function setNamespace(string $namespace): void | ||
| 87 | { | ||
| 88 | $this->namespace = $namespace; | ||
| 89 | } | ||
| 90 | |||
| 91 | public function isTransient(string $className): bool | ||
| 92 | { | ||
| 93 | return true; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * {@inheritDoc} | ||
| 98 | */ | ||
| 99 | public function getAllClassNames(): array | ||
| 100 | { | ||
| 101 | $this->reverseEngineerMappingFromDatabase(); | ||
| 102 | |||
| 103 | return array_keys($this->classToTableNames); | ||
| 104 | } | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Sets class name for a table. | ||
| 108 | */ | ||
| 109 | public function setClassNameForTable(string $tableName, string $className): void | ||
| 110 | { | ||
| 111 | $this->classNamesForTables[$tableName] = $className; | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Sets field name for a column on a specific table. | ||
| 116 | */ | ||
| 117 | public function setFieldNameForColumn(string $tableName, string $columnName, string $fieldName): void | ||
| 118 | { | ||
| 119 | $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; | ||
| 120 | } | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager. | ||
| 124 | * | ||
| 125 | * @param Table[] $entityTables | ||
| 126 | * @param Table[] $manyToManyTables | ||
| 127 | * @psalm-param list<Table> $entityTables | ||
| 128 | * @psalm-param list<Table> $manyToManyTables | ||
| 129 | */ | ||
| 130 | public function setTables(array $entityTables, array $manyToManyTables): void | ||
| 131 | { | ||
| 132 | $this->tables = $this->manyToManyTables = $this->classToTableNames = []; | ||
| 133 | |||
| 134 | foreach ($entityTables as $table) { | ||
| 135 | $className = $this->getClassNameForTable($table->getName()); | ||
| 136 | |||
| 137 | $this->classToTableNames[$className] = $table->getName(); | ||
| 138 | $this->tables[$table->getName()] = $table; | ||
| 139 | } | ||
| 140 | |||
| 141 | foreach ($manyToManyTables as $table) { | ||
| 142 | $this->manyToManyTables[$table->getName()] = $table; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | public function setInflector(Inflector $inflector): void | ||
| 147 | { | ||
| 148 | $this->inflector = $inflector; | ||
| 149 | } | ||
| 150 | |||
| 151 | /** | ||
| 152 | * {@inheritDoc} | ||
| 153 | * | ||
| 154 | * @psalm-param class-string<T> $className | ||
| 155 | * @psalm-param ClassMetadata<T> $metadata | ||
| 156 | * | ||
| 157 | * @template T of object | ||
| 158 | */ | ||
| 159 | public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void | ||
| 160 | { | ||
| 161 | if (! $metadata instanceof ClassMetadata) { | ||
| 162 | throw new TypeError(sprintf( | ||
| 163 | 'Argument #2 passed to %s() must be an instance of %s, %s given.', | ||
| 164 | __METHOD__, | ||
| 165 | ClassMetadata::class, | ||
| 166 | get_debug_type($metadata), | ||
| 167 | )); | ||
| 168 | } | ||
| 169 | |||
| 170 | $this->reverseEngineerMappingFromDatabase(); | ||
| 171 | |||
| 172 | if (! isset($this->classToTableNames[$className])) { | ||
| 173 | throw new InvalidArgumentException('Unknown class ' . $className); | ||
| 174 | } | ||
| 175 | |||
| 176 | $tableName = $this->classToTableNames[$className]; | ||
| 177 | |||
| 178 | $metadata->name = $className; | ||
| 179 | $metadata->table['name'] = $tableName; | ||
| 180 | |||
| 181 | $this->buildIndexes($metadata); | ||
| 182 | $this->buildFieldMappings($metadata); | ||
| 183 | $this->buildToOneAssociationMappings($metadata); | ||
| 184 | |||
| 185 | foreach ($this->manyToManyTables as $manyTable) { | ||
| 186 | foreach ($manyTable->getForeignKeys() as $foreignKey) { | ||
| 187 | // foreign key maps to the table of the current entity, many to many association probably exists | ||
| 188 | if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) { | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | |||
| 192 | $myFk = $foreignKey; | ||
| 193 | $otherFk = null; | ||
| 194 | |||
| 195 | foreach ($manyTable->getForeignKeys() as $foreignKey) { | ||
| 196 | if ($foreignKey !== $myFk) { | ||
| 197 | $otherFk = $foreignKey; | ||
| 198 | break; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | if (! $otherFk) { | ||
| 203 | // the definition of this many to many table does not contain | ||
| 204 | // enough foreign key information to continue reverse engineering. | ||
| 205 | continue; | ||
| 206 | } | ||
| 207 | |||
| 208 | $localColumn = current($myFk->getLocalColumns()); | ||
| 209 | |||
| 210 | $associationMapping = []; | ||
| 211 | $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true); | ||
| 212 | $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); | ||
| 213 | |||
| 214 | if (current($manyTable->getColumns())->getName() === $localColumn) { | ||
| 215 | $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); | ||
| 216 | $associationMapping['joinTable'] = [ | ||
| 217 | 'name' => strtolower($manyTable->getName()), | ||
| 218 | 'joinColumns' => [], | ||
| 219 | 'inverseJoinColumns' => [], | ||
| 220 | ]; | ||
| 221 | |||
| 222 | $fkCols = $myFk->getForeignColumns(); | ||
| 223 | $cols = $myFk->getLocalColumns(); | ||
| 224 | |||
| 225 | for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { | ||
| 226 | $associationMapping['joinTable']['joinColumns'][] = [ | ||
| 227 | 'name' => $cols[$i], | ||
| 228 | 'referencedColumnName' => $fkCols[$i], | ||
| 229 | ]; | ||
| 230 | } | ||
| 231 | |||
| 232 | $fkCols = $otherFk->getForeignColumns(); | ||
| 233 | $cols = $otherFk->getLocalColumns(); | ||
| 234 | |||
| 235 | for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { | ||
| 236 | $associationMapping['joinTable']['inverseJoinColumns'][] = [ | ||
| 237 | 'name' => $cols[$i], | ||
| 238 | 'referencedColumnName' => $fkCols[$i], | ||
| 239 | ]; | ||
| 240 | } | ||
| 241 | } else { | ||
| 242 | $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); | ||
| 243 | } | ||
| 244 | |||
| 245 | $metadata->mapManyToMany($associationMapping); | ||
| 246 | |||
| 247 | break; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | /** @throws MappingException */ | ||
| 253 | private function reverseEngineerMappingFromDatabase(): void | ||
| 254 | { | ||
| 255 | if ($this->tables !== null) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | $this->tables = $this->manyToManyTables = $this->classToTableNames = []; | ||
| 260 | |||
| 261 | foreach ($this->sm->listTables() as $table) { | ||
| 262 | $tableName = $table->getName(); | ||
| 263 | $foreignKeys = $table->getForeignKeys(); | ||
| 264 | |||
| 265 | $allForeignKeyColumns = []; | ||
| 266 | |||
| 267 | foreach ($foreignKeys as $foreignKey) { | ||
| 268 | $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); | ||
| 269 | } | ||
| 270 | |||
| 271 | $primaryKey = $table->getPrimaryKey(); | ||
| 272 | if ($primaryKey === null) { | ||
| 273 | throw new MappingException( | ||
| 274 | 'Table ' . $tableName . ' has no primary key. Doctrine does not ' . | ||
| 275 | "support reverse engineering from tables that don't have a primary key.", | ||
| 276 | ); | ||
| 277 | } | ||
| 278 | |||
| 279 | $pkColumns = $primaryKey->getColumns(); | ||
| 280 | |||
| 281 | sort($pkColumns); | ||
| 282 | sort($allForeignKeyColumns); | ||
| 283 | |||
| 284 | if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) { | ||
| 285 | $this->manyToManyTables[$tableName] = $table; | ||
| 286 | } else { | ||
| 287 | // lower-casing is necessary because of Oracle Uppercase Tablenames, | ||
| 288 | // assumption is lower-case + underscore separated. | ||
| 289 | $className = $this->getClassNameForTable($tableName); | ||
| 290 | |||
| 291 | $this->tables[$tableName] = $table; | ||
| 292 | $this->classToTableNames[$className] = $tableName; | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | /** | ||
| 298 | * Build indexes from a class metadata. | ||
| 299 | */ | ||
| 300 | private function buildIndexes(ClassMetadata $metadata): void | ||
| 301 | { | ||
| 302 | $tableName = $metadata->table['name']; | ||
| 303 | $indexes = $this->tables[$tableName]->getIndexes(); | ||
| 304 | |||
| 305 | foreach ($indexes as $index) { | ||
| 306 | if ($index->isPrimary()) { | ||
| 307 | continue; | ||
| 308 | } | ||
| 309 | |||
| 310 | $indexName = $index->getName(); | ||
| 311 | $indexColumns = $index->getColumns(); | ||
| 312 | $constraintType = $index->isUnique() | ||
| 313 | ? 'uniqueConstraints' | ||
| 314 | : 'indexes'; | ||
| 315 | |||
| 316 | $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns; | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | /** | ||
| 321 | * Build field mapping from class metadata. | ||
| 322 | */ | ||
| 323 | private function buildFieldMappings(ClassMetadata $metadata): void | ||
| 324 | { | ||
| 325 | $tableName = $metadata->table['name']; | ||
| 326 | $columns = $this->tables[$tableName]->getColumns(); | ||
| 327 | $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); | ||
| 328 | $foreignKeys = $this->tables[$tableName]->getForeignKeys(); | ||
| 329 | $allForeignKeys = []; | ||
| 330 | |||
| 331 | foreach ($foreignKeys as $foreignKey) { | ||
| 332 | $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns()); | ||
| 333 | } | ||
| 334 | |||
| 335 | $ids = []; | ||
| 336 | $fieldMappings = []; | ||
| 337 | |||
| 338 | foreach ($columns as $column) { | ||
| 339 | if (in_array($column->getName(), $allForeignKeys, true)) { | ||
| 340 | continue; | ||
| 341 | } | ||
| 342 | |||
| 343 | $fieldMapping = $this->buildFieldMapping($tableName, $column); | ||
| 344 | |||
| 345 | if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) { | ||
| 346 | $fieldMapping['id'] = true; | ||
| 347 | $ids[] = $fieldMapping; | ||
| 348 | } | ||
| 349 | |||
| 350 | $fieldMappings[] = $fieldMapping; | ||
| 351 | } | ||
| 352 | |||
| 353 | // We need to check for the columns here, because we might have associations as id as well. | ||
| 354 | if ($ids && count($primaryKeys) === 1) { | ||
| 355 | $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); | ||
| 356 | } | ||
| 357 | |||
| 358 | foreach ($fieldMappings as $fieldMapping) { | ||
| 359 | $metadata->mapField($fieldMapping); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | /** | ||
| 364 | * Build field mapping from a schema column definition | ||
| 365 | * | ||
| 366 | * @return mixed[] | ||
| 367 | * @psalm-return array{ | ||
| 368 | * fieldName: string, | ||
| 369 | * columnName: string, | ||
| 370 | * type: string, | ||
| 371 | * nullable: bool, | ||
| 372 | * options: array{ | ||
| 373 | * unsigned?: bool, | ||
| 374 | * fixed?: bool, | ||
| 375 | * comment: string|null, | ||
| 376 | * default?: mixed | ||
| 377 | * }, | ||
| 378 | * precision?: int, | ||
| 379 | * scale?: int, | ||
| 380 | * length?: int|null | ||
| 381 | * } | ||
| 382 | */ | ||
| 383 | private function buildFieldMapping(string $tableName, Column $column): array | ||
| 384 | { | ||
| 385 | $fieldMapping = [ | ||
| 386 | 'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false), | ||
| 387 | 'columnName' => $column->getName(), | ||
| 388 | 'type' => Type::getTypeRegistry()->lookupName($column->getType()), | ||
| 389 | 'nullable' => ! $column->getNotnull(), | ||
| 390 | 'options' => [ | ||
| 391 | 'comment' => $column->getComment(), | ||
| 392 | ], | ||
| 393 | ]; | ||
| 394 | |||
| 395 | // Type specific elements | ||
| 396 | switch ($fieldMapping['type']) { | ||
| 397 | case self::ARRAY: | ||
| 398 | case Types::BLOB: | ||
| 399 | case Types::GUID: | ||
| 400 | case self::OBJECT: | ||
| 401 | case Types::SIMPLE_ARRAY: | ||
| 402 | case Types::STRING: | ||
| 403 | case Types::TEXT: | ||
| 404 | $fieldMapping['length'] = $column->getLength(); | ||
| 405 | $fieldMapping['options']['fixed'] = $column->getFixed(); | ||
| 406 | break; | ||
| 407 | |||
| 408 | case Types::DECIMAL: | ||
| 409 | case Types::FLOAT: | ||
| 410 | $fieldMapping['precision'] = $column->getPrecision(); | ||
| 411 | $fieldMapping['scale'] = $column->getScale(); | ||
| 412 | break; | ||
| 413 | |||
| 414 | case Types::INTEGER: | ||
| 415 | case Types::BIGINT: | ||
| 416 | case Types::SMALLINT: | ||
| 417 | $fieldMapping['options']['unsigned'] = $column->getUnsigned(); | ||
| 418 | break; | ||
| 419 | } | ||
| 420 | |||
| 421 | // Default | ||
| 422 | $default = $column->getDefault(); | ||
| 423 | if ($default !== null) { | ||
| 424 | $fieldMapping['options']['default'] = $default; | ||
| 425 | } | ||
| 426 | |||
| 427 | return $fieldMapping; | ||
| 428 | } | ||
| 429 | |||
| 430 | /** | ||
| 431 | * Build to one (one to one, many to one) association mapping from class metadata. | ||
| 432 | */ | ||
| 433 | private function buildToOneAssociationMappings(ClassMetadata $metadata): void | ||
| 434 | { | ||
| 435 | assert($this->tables !== null); | ||
| 436 | |||
| 437 | $tableName = $metadata->table['name']; | ||
| 438 | $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); | ||
| 439 | $foreignKeys = $this->tables[$tableName]->getForeignKeys(); | ||
| 440 | |||
| 441 | foreach ($foreignKeys as $foreignKey) { | ||
| 442 | $foreignTableName = $foreignKey->getForeignTableName(); | ||
| 443 | $fkColumns = $foreignKey->getLocalColumns(); | ||
| 444 | $fkForeignColumns = $foreignKey->getForeignColumns(); | ||
| 445 | $localColumn = current($fkColumns); | ||
| 446 | $associationMapping = [ | ||
| 447 | 'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true), | ||
| 448 | 'targetEntity' => $this->getClassNameForTable($foreignTableName), | ||
| 449 | ]; | ||
| 450 | |||
| 451 | if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { | ||
| 452 | $associationMapping['fieldName'] .= '2'; // "foo" => "foo2" | ||
| 453 | } | ||
| 454 | |||
| 455 | if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) { | ||
| 456 | $associationMapping['id'] = true; | ||
| 457 | } | ||
| 458 | |||
| 459 | for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) { | ||
| 460 | $associationMapping['joinColumns'][] = [ | ||
| 461 | 'name' => $fkColumns[$i], | ||
| 462 | 'referencedColumnName' => $fkForeignColumns[$i], | ||
| 463 | ]; | ||
| 464 | } | ||
| 465 | |||
| 466 | // Here we need to check if $fkColumns are the same as $primaryKeys | ||
| 467 | if (! array_diff($fkColumns, $primaryKeys)) { | ||
| 468 | $metadata->mapOneToOne($associationMapping); | ||
| 469 | } else { | ||
| 470 | $metadata->mapManyToOne($associationMapping); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | } | ||
| 474 | |||
| 475 | /** | ||
| 476 | * Retrieve schema table definition primary keys. | ||
| 477 | * | ||
| 478 | * @return string[] | ||
| 479 | */ | ||
| 480 | private function getTablePrimaryKeys(Table $table): array | ||
| 481 | { | ||
| 482 | try { | ||
| 483 | return $table->getPrimaryKey()->getColumns(); | ||
| 484 | } catch (SchemaException) { | ||
| 485 | // Do nothing | ||
| 486 | } | ||
| 487 | |||
| 488 | return []; | ||
| 489 | } | ||
| 490 | |||
| 491 | /** | ||
| 492 | * Returns the mapped class name for a table if it exists. Otherwise return "classified" version. | ||
| 493 | * | ||
| 494 | * @psalm-return class-string | ||
| 495 | */ | ||
| 496 | private function getClassNameForTable(string $tableName): string | ||
| 497 | { | ||
| 498 | if (isset($this->classNamesForTables[$tableName])) { | ||
| 499 | return $this->namespace . $this->classNamesForTables[$tableName]; | ||
| 500 | } | ||
| 501 | |||
| 502 | return $this->namespace . $this->inflector->classify(strtolower($tableName)); | ||
| 503 | } | ||
| 504 | |||
| 505 | /** | ||
| 506 | * Return the mapped field name for a column, if it exists. Otherwise return camelized version. | ||
| 507 | * | ||
| 508 | * @param bool $fk Whether the column is a foreignkey or not. | ||
| 509 | */ | ||
| 510 | private function getFieldNameForColumn( | ||
| 511 | string $tableName, | ||
| 512 | string $columnName, | ||
| 513 | bool $fk = false, | ||
| 514 | ): string { | ||
| 515 | if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) { | ||
| 516 | return $this->fieldNamesForColumns[$tableName][$columnName]; | ||
| 517 | } | ||
| 518 | |||
| 519 | $columnName = strtolower($columnName); | ||
| 520 | |||
| 521 | // Replace _id if it is a foreignkey column | ||
| 522 | if ($fk) { | ||
| 523 | $columnName = preg_replace('/_id$/', '', $columnName); | ||
| 524 | } | ||
| 525 | |||
| 526 | return $this->inflector->camelize($columnName); | ||
| 527 | } | ||
| 528 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 8 | use ReflectionProperty; | ||
| 9 | |||
| 10 | /** @internal */ | ||
| 11 | trait ReflectionBasedDriver | ||
| 12 | { | ||
| 13 | /** | ||
| 14 | * Helps to deal with the case that reflection may report properties inherited from parent classes. | ||
| 15 | * When we know about the fields already (inheritance has been anticipated in ClassMetadataFactory), | ||
| 16 | * the driver must skip them. | ||
| 17 | * | ||
| 18 | * The declaring classes may mismatch when there are private properties: The same property name may be | ||
| 19 | * reported multiple times, but since it is private, it is in fact multiple (different) properties in | ||
| 20 | * different classes. In that case, report the property as an individual field. (ClassMetadataFactory will | ||
| 21 | * probably fail in that case, though.) | ||
| 22 | */ | ||
| 23 | private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool | ||
| 24 | { | ||
| 25 | $declaringClass = $property->class; | ||
| 26 | |||
| 27 | if ( | ||
| 28 | isset($metadata->fieldMappings[$property->name]->declared) | ||
| 29 | && $metadata->fieldMappings[$property->name]->declared === $declaringClass | ||
| 30 | ) { | ||
| 31 | return true; | ||
| 32 | } | ||
| 33 | |||
| 34 | if ( | ||
| 35 | isset($metadata->associationMappings[$property->name]->declared) | ||
| 36 | && $metadata->associationMappings[$property->name]->declared === $declaringClass | ||
| 37 | ) { | ||
| 38 | return true; | ||
| 39 | } | ||
| 40 | |||
| 41 | return isset($metadata->embeddedClasses[$property->name]->declared) | ||
| 42 | && $metadata->embeddedClasses[$property->name]->declared === $declaringClass; | ||
| 43 | } | ||
| 44 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use ArrayObject; | ||
| 8 | use Doctrine\ORM\Mapping\MappingAttribute; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * @template-extends ArrayObject<int, T> | ||
| 12 | * @template T of MappingAttribute | ||
| 13 | */ | ||
| 14 | final class RepeatableAttributeCollection extends ArrayObject | ||
| 15 | { | ||
| 16 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * XmlDriver that additionally looks for mapping information in a global file. | ||
| 11 | */ | ||
| 12 | class SimplifiedXmlDriver extends XmlDriver | ||
| 13 | { | ||
| 14 | public const DEFAULT_FILE_EXTENSION = '.orm.xml'; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * {@inheritDoc} | ||
| 18 | */ | ||
| 19 | public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = true) | ||
| 20 | { | ||
| 21 | $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); | ||
| 22 | |||
| 23 | parent::__construct($locator, $fileExtension, $isXsdValidationEnabled); | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/Driver/XmlDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/XmlDriver.php new file mode 100644 index 0000000..ff473ce --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Driver/XmlDriver.php | |||
| @@ -0,0 +1,940 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Driver; | ||
| 6 | |||
| 7 | use Doctrine\Common\Collections\Criteria; | ||
| 8 | use Doctrine\Common\Collections\Order; | ||
| 9 | use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; | ||
| 10 | use Doctrine\ORM\Mapping\ClassMetadata; | ||
| 11 | use Doctrine\ORM\Mapping\MappingException; | ||
| 12 | use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; | ||
| 13 | use Doctrine\Persistence\Mapping\Driver\FileDriver; | ||
| 14 | use Doctrine\Persistence\Mapping\Driver\FileLocator; | ||
| 15 | use DOMDocument; | ||
| 16 | use InvalidArgumentException; | ||
| 17 | use LogicException; | ||
| 18 | use SimpleXMLElement; | ||
| 19 | |||
| 20 | use function assert; | ||
| 21 | use function constant; | ||
| 22 | use function count; | ||
| 23 | use function defined; | ||
| 24 | use function enum_exists; | ||
| 25 | use function explode; | ||
| 26 | use function extension_loaded; | ||
| 27 | use function file_get_contents; | ||
| 28 | use function in_array; | ||
| 29 | use function libxml_clear_errors; | ||
| 30 | use function libxml_get_errors; | ||
| 31 | use function libxml_use_internal_errors; | ||
| 32 | use function simplexml_load_string; | ||
| 33 | use function sprintf; | ||
| 34 | use function str_replace; | ||
| 35 | use function strtoupper; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * XmlDriver is a metadata driver that enables mapping through XML files. | ||
| 39 | * | ||
| 40 | * @link www.doctrine-project.org | ||
| 41 | * | ||
| 42 | * @template-extends FileDriver<SimpleXMLElement> | ||
| 43 | */ | ||
| 44 | class XmlDriver extends FileDriver | ||
| 45 | { | ||
| 46 | public const DEFAULT_FILE_EXTENSION = '.dcm.xml'; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * {@inheritDoc} | ||
| 50 | */ | ||
| 51 | public function __construct( | ||
| 52 | string|array|FileLocator $locator, | ||
| 53 | string $fileExtension = self::DEFAULT_FILE_EXTENSION, | ||
| 54 | private readonly bool $isXsdValidationEnabled = true, | ||
| 55 | ) { | ||
| 56 | if (! extension_loaded('simplexml')) { | ||
| 57 | throw new LogicException( | ||
| 58 | 'The XML metadata driver cannot be enabled because the SimpleXML PHP extension is missing.' | ||
| 59 | . ' Please configure PHP with SimpleXML or choose a different metadata driver.', | ||
| 60 | ); | ||
| 61 | } | ||
| 62 | |||
| 63 | if ($isXsdValidationEnabled && ! extension_loaded('dom')) { | ||
| 64 | throw new LogicException( | ||
| 65 | 'XSD validation cannot be enabled because the DOM extension is missing.', | ||
| 66 | ); | ||
| 67 | } | ||
| 68 | |||
| 69 | parent::__construct($locator, $fileExtension); | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * {@inheritDoc} | ||
| 74 | * | ||
| 75 | * @psalm-param class-string<T> $className | ||
| 76 | * @psalm-param ClassMetadata<T> $metadata | ||
| 77 | * | ||
| 78 | * @template T of object | ||
| 79 | */ | ||
| 80 | public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void | ||
| 81 | { | ||
| 82 | $xmlRoot = $this->getElement($className); | ||
| 83 | |||
| 84 | if ($xmlRoot->getName() === 'entity') { | ||
| 85 | if (isset($xmlRoot['repository-class'])) { | ||
| 86 | $metadata->setCustomRepositoryClass((string) $xmlRoot['repository-class']); | ||
| 87 | } | ||
| 88 | |||
| 89 | if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) { | ||
| 90 | $metadata->markReadOnly(); | ||
| 91 | } | ||
| 92 | } elseif ($xmlRoot->getName() === 'mapped-superclass') { | ||
| 93 | $metadata->setCustomRepositoryClass( | ||
| 94 | isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null, | ||
| 95 | ); | ||
| 96 | $metadata->isMappedSuperclass = true; | ||
| 97 | } elseif ($xmlRoot->getName() === 'embeddable') { | ||
| 98 | $metadata->isEmbeddedClass = true; | ||
| 99 | } else { | ||
| 100 | throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Evaluate <entity...> attributes | ||
| 104 | $primaryTable = []; | ||
| 105 | |||
| 106 | if (isset($xmlRoot['table'])) { | ||
| 107 | $primaryTable['name'] = (string) $xmlRoot['table']; | ||
| 108 | } | ||
| 109 | |||
| 110 | if (isset($xmlRoot['schema'])) { | ||
| 111 | $primaryTable['schema'] = (string) $xmlRoot['schema']; | ||
| 112 | } | ||
| 113 | |||
| 114 | $metadata->setPrimaryTable($primaryTable); | ||
| 115 | |||
| 116 | // Evaluate second level cache | ||
| 117 | if (isset($xmlRoot->cache)) { | ||
| 118 | $metadata->enableCache($this->cacheToArray($xmlRoot->cache)); | ||
| 119 | } | ||
| 120 | |||
| 121 | if (isset($xmlRoot['inheritance-type'])) { | ||
| 122 | $inheritanceType = (string) $xmlRoot['inheritance-type']; | ||
| 123 | $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); | ||
| 124 | |||
| 125 | if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { | ||
| 126 | // Evaluate <discriminator-column...> | ||
| 127 | if (isset($xmlRoot->{'discriminator-column'})) { | ||
| 128 | $discrColumn = $xmlRoot->{'discriminator-column'}; | ||
| 129 | $columnDef = [ | ||
| 130 | 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, | ||
| 131 | 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', | ||
| 132 | 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, | ||
| 133 | 'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null, | ||
| 134 | 'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null, | ||
| 135 | ]; | ||
| 136 | |||
| 137 | if (isset($discrColumn['options'])) { | ||
| 138 | assert($discrColumn['options'] instanceof SimpleXMLElement); | ||
| 139 | $columnDef['options'] = $this->parseOptions($discrColumn['options']->children()); | ||
| 140 | } | ||
| 141 | |||
| 142 | $metadata->setDiscriminatorColumn($columnDef); | ||
| 143 | } else { | ||
| 144 | $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); | ||
| 145 | } | ||
| 146 | |||
| 147 | // Evaluate <discriminator-map...> | ||
| 148 | if (isset($xmlRoot->{'discriminator-map'})) { | ||
| 149 | $map = []; | ||
| 150 | assert($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} instanceof SimpleXMLElement); | ||
| 151 | foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) { | ||
| 152 | $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class']; | ||
| 153 | } | ||
| 154 | |||
| 155 | $metadata->setDiscriminatorMap($map); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | // Evaluate <change-tracking-policy...> | ||
| 161 | if (isset($xmlRoot['change-tracking-policy'])) { | ||
| 162 | $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' | ||
| 163 | . strtoupper((string) $xmlRoot['change-tracking-policy']))); | ||
| 164 | } | ||
| 165 | |||
| 166 | // Evaluate <indexes...> | ||
| 167 | if (isset($xmlRoot->indexes)) { | ||
| 168 | $metadata->table['indexes'] = []; | ||
| 169 | foreach ($xmlRoot->indexes->index ?? [] as $indexXml) { | ||
| 170 | $index = []; | ||
| 171 | |||
| 172 | if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) { | ||
| 173 | $index['columns'] = explode(',', (string) $indexXml['columns']); | ||
| 174 | } | ||
| 175 | |||
| 176 | if (isset($indexXml['fields'])) { | ||
| 177 | $index['fields'] = explode(',', (string) $indexXml['fields']); | ||
| 178 | } | ||
| 179 | |||
| 180 | if ( | ||
| 181 | isset($index['columns'], $index['fields']) | ||
| 182 | || ( | ||
| 183 | ! isset($index['columns']) | ||
| 184 | && ! isset($index['fields']) | ||
| 185 | ) | ||
| 186 | ) { | ||
| 187 | throw MappingException::invalidIndexConfiguration( | ||
| 188 | $className, | ||
| 189 | (string) ($indexXml['name'] ?? count($metadata->table['indexes'])), | ||
| 190 | ); | ||
| 191 | } | ||
| 192 | |||
| 193 | if (isset($indexXml['flags'])) { | ||
| 194 | $index['flags'] = explode(',', (string) $indexXml['flags']); | ||
| 195 | } | ||
| 196 | |||
| 197 | if (isset($indexXml->options)) { | ||
| 198 | $index['options'] = $this->parseOptions($indexXml->options->children()); | ||
| 199 | } | ||
| 200 | |||
| 201 | if (isset($indexXml['name'])) { | ||
| 202 | $metadata->table['indexes'][(string) $indexXml['name']] = $index; | ||
| 203 | } else { | ||
| 204 | $metadata->table['indexes'][] = $index; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | // Evaluate <unique-constraints..> | ||
| 210 | if (isset($xmlRoot->{'unique-constraints'})) { | ||
| 211 | $metadata->table['uniqueConstraints'] = []; | ||
| 212 | foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} ?? [] as $uniqueXml) { | ||
| 213 | $unique = []; | ||
| 214 | |||
| 215 | if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) { | ||
| 216 | $unique['columns'] = explode(',', (string) $uniqueXml['columns']); | ||
| 217 | } | ||
| 218 | |||
| 219 | if (isset($uniqueXml['fields'])) { | ||
| 220 | $unique['fields'] = explode(',', (string) $uniqueXml['fields']); | ||
| 221 | } | ||
| 222 | |||
| 223 | if ( | ||
| 224 | isset($unique['columns'], $unique['fields']) | ||
| 225 | || ( | ||
| 226 | ! isset($unique['columns']) | ||
| 227 | && ! isset($unique['fields']) | ||
| 228 | ) | ||
| 229 | ) { | ||
| 230 | throw MappingException::invalidUniqueConstraintConfiguration( | ||
| 231 | $className, | ||
| 232 | (string) ($uniqueXml['name'] ?? count($metadata->table['uniqueConstraints'])), | ||
| 233 | ); | ||
| 234 | } | ||
| 235 | |||
| 236 | if (isset($uniqueXml->options)) { | ||
| 237 | $unique['options'] = $this->parseOptions($uniqueXml->options->children()); | ||
| 238 | } | ||
| 239 | |||
| 240 | if (isset($uniqueXml['name'])) { | ||
| 241 | $metadata->table['uniqueConstraints'][(string) $uniqueXml['name']] = $unique; | ||
| 242 | } else { | ||
| 243 | $metadata->table['uniqueConstraints'][] = $unique; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | } | ||
| 247 | |||
| 248 | if (isset($xmlRoot->options)) { | ||
| 249 | $metadata->table['options'] = $this->parseOptions($xmlRoot->options->children()); | ||
| 250 | } | ||
| 251 | |||
| 252 | // The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions | ||
| 253 | // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception | ||
| 254 | // Evaluate <field ...> mappings | ||
| 255 | if (isset($xmlRoot->field)) { | ||
| 256 | foreach ($xmlRoot->field as $fieldMapping) { | ||
| 257 | $mapping = $this->columnToArray($fieldMapping); | ||
| 258 | |||
| 259 | if (isset($mapping['version'])) { | ||
| 260 | $metadata->setVersionMapping($mapping); | ||
| 261 | unset($mapping['version']); | ||
| 262 | } | ||
| 263 | |||
| 264 | $metadata->mapField($mapping); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | if (isset($xmlRoot->embedded)) { | ||
| 269 | foreach ($xmlRoot->embedded as $embeddedMapping) { | ||
| 270 | $columnPrefix = isset($embeddedMapping['column-prefix']) | ||
| 271 | ? (string) $embeddedMapping['column-prefix'] | ||
| 272 | : null; | ||
| 273 | |||
| 274 | $useColumnPrefix = isset($embeddedMapping['use-column-prefix']) | ||
| 275 | ? $this->evaluateBoolean($embeddedMapping['use-column-prefix']) | ||
| 276 | : true; | ||
| 277 | |||
| 278 | $mapping = [ | ||
| 279 | 'fieldName' => (string) $embeddedMapping['name'], | ||
| 280 | 'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null, | ||
| 281 | 'columnPrefix' => $useColumnPrefix ? $columnPrefix : false, | ||
| 282 | ]; | ||
| 283 | |||
| 284 | $metadata->mapEmbedded($mapping); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | // Evaluate <id ...> mappings | ||
| 289 | $associationIds = []; | ||
| 290 | foreach ($xmlRoot->id ?? [] as $idElement) { | ||
| 291 | if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) { | ||
| 292 | $associationIds[(string) $idElement['name']] = true; | ||
| 293 | continue; | ||
| 294 | } | ||
| 295 | |||
| 296 | $mapping = $this->columnToArray($idElement); | ||
| 297 | $mapping['id'] = true; | ||
| 298 | |||
| 299 | $metadata->mapField($mapping); | ||
| 300 | |||
| 301 | if (isset($idElement->generator)) { | ||
| 302 | $strategy = isset($idElement->generator['strategy']) ? | ||
| 303 | (string) $idElement->generator['strategy'] : 'AUTO'; | ||
| 304 | $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' | ||
| 305 | . $strategy)); | ||
| 306 | } | ||
| 307 | |||
| 308 | // Check for SequenceGenerator/TableGenerator definition | ||
| 309 | if (isset($idElement->{'sequence-generator'})) { | ||
| 310 | $seqGenerator = $idElement->{'sequence-generator'}; | ||
| 311 | $metadata->setSequenceGeneratorDefinition( | ||
| 312 | [ | ||
| 313 | 'sequenceName' => (string) $seqGenerator['sequence-name'], | ||
| 314 | 'allocationSize' => (string) $seqGenerator['allocation-size'], | ||
| 315 | 'initialValue' => (string) $seqGenerator['initial-value'], | ||
| 316 | ], | ||
| 317 | ); | ||
| 318 | } elseif (isset($idElement->{'custom-id-generator'})) { | ||
| 319 | $customGenerator = $idElement->{'custom-id-generator'}; | ||
| 320 | $metadata->setCustomGeneratorDefinition( | ||
| 321 | [ | ||
| 322 | 'class' => (string) $customGenerator['class'], | ||
| 323 | ], | ||
| 324 | ); | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | // Evaluate <one-to-one ...> mappings | ||
| 329 | if (isset($xmlRoot->{'one-to-one'})) { | ||
| 330 | foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) { | ||
| 331 | $mapping = [ | ||
| 332 | 'fieldName' => (string) $oneToOneElement['field'], | ||
| 333 | ]; | ||
| 334 | |||
| 335 | if (isset($oneToOneElement['target-entity'])) { | ||
| 336 | $mapping['targetEntity'] = (string) $oneToOneElement['target-entity']; | ||
| 337 | } | ||
| 338 | |||
| 339 | if (isset($associationIds[$mapping['fieldName']])) { | ||
| 340 | $mapping['id'] = true; | ||
| 341 | } | ||
| 342 | |||
| 343 | if (isset($oneToOneElement['fetch'])) { | ||
| 344 | $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToOneElement['fetch']); | ||
| 345 | } | ||
| 346 | |||
| 347 | if (isset($oneToOneElement['mapped-by'])) { | ||
| 348 | $mapping['mappedBy'] = (string) $oneToOneElement['mapped-by']; | ||
| 349 | } else { | ||
| 350 | if (isset($oneToOneElement['inversed-by'])) { | ||
| 351 | $mapping['inversedBy'] = (string) $oneToOneElement['inversed-by']; | ||
| 352 | } | ||
| 353 | |||
| 354 | $joinColumns = []; | ||
| 355 | |||
| 356 | if (isset($oneToOneElement->{'join-column'})) { | ||
| 357 | $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'}); | ||
| 358 | } elseif (isset($oneToOneElement->{'join-columns'})) { | ||
| 359 | foreach ($oneToOneElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 360 | $joinColumns[] = $this->joinColumnToArray($joinColumnElement); | ||
| 361 | } | ||
| 362 | } | ||
| 363 | |||
| 364 | $mapping['joinColumns'] = $joinColumns; | ||
| 365 | } | ||
| 366 | |||
| 367 | if (isset($oneToOneElement->cascade)) { | ||
| 368 | $mapping['cascade'] = $this->getCascadeMappings($oneToOneElement->cascade); | ||
| 369 | } | ||
| 370 | |||
| 371 | if (isset($oneToOneElement['orphan-removal'])) { | ||
| 372 | $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']); | ||
| 373 | } | ||
| 374 | |||
| 375 | // Evaluate second level cache | ||
| 376 | if (isset($oneToOneElement->cache)) { | ||
| 377 | $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache)); | ||
| 378 | } | ||
| 379 | |||
| 380 | $metadata->mapOneToOne($mapping); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | // Evaluate <one-to-many ...> mappings | ||
| 385 | if (isset($xmlRoot->{'one-to-many'})) { | ||
| 386 | foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) { | ||
| 387 | $mapping = [ | ||
| 388 | 'fieldName' => (string) $oneToManyElement['field'], | ||
| 389 | 'mappedBy' => (string) $oneToManyElement['mapped-by'], | ||
| 390 | ]; | ||
| 391 | |||
| 392 | if (isset($oneToManyElement['target-entity'])) { | ||
| 393 | $mapping['targetEntity'] = (string) $oneToManyElement['target-entity']; | ||
| 394 | } | ||
| 395 | |||
| 396 | if (isset($oneToManyElement['fetch'])) { | ||
| 397 | $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToManyElement['fetch']); | ||
| 398 | } | ||
| 399 | |||
| 400 | if (isset($oneToManyElement->cascade)) { | ||
| 401 | $mapping['cascade'] = $this->getCascadeMappings($oneToManyElement->cascade); | ||
| 402 | } | ||
| 403 | |||
| 404 | if (isset($oneToManyElement['orphan-removal'])) { | ||
| 405 | $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']); | ||
| 406 | } | ||
| 407 | |||
| 408 | if (isset($oneToManyElement->{'order-by'})) { | ||
| 409 | $orderBy = []; | ||
| 410 | foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) { | ||
| 411 | /** @psalm-suppress DeprecatedConstant */ | ||
| 412 | $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) | ||
| 413 | ? (string) $orderByField['direction'] | ||
| 414 | : (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC); | ||
| 415 | } | ||
| 416 | |||
| 417 | $mapping['orderBy'] = $orderBy; | ||
| 418 | } | ||
| 419 | |||
| 420 | if (isset($oneToManyElement['index-by'])) { | ||
| 421 | $mapping['indexBy'] = (string) $oneToManyElement['index-by']; | ||
| 422 | } elseif (isset($oneToManyElement->{'index-by'})) { | ||
| 423 | throw new InvalidArgumentException('<index-by /> is not a valid tag'); | ||
| 424 | } | ||
| 425 | |||
| 426 | // Evaluate second level cache | ||
| 427 | if (isset($oneToManyElement->cache)) { | ||
| 428 | $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache)); | ||
| 429 | } | ||
| 430 | |||
| 431 | $metadata->mapOneToMany($mapping); | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | // Evaluate <many-to-one ...> mappings | ||
| 436 | if (isset($xmlRoot->{'many-to-one'})) { | ||
| 437 | foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) { | ||
| 438 | $mapping = [ | ||
| 439 | 'fieldName' => (string) $manyToOneElement['field'], | ||
| 440 | ]; | ||
| 441 | |||
| 442 | if (isset($manyToOneElement['target-entity'])) { | ||
| 443 | $mapping['targetEntity'] = (string) $manyToOneElement['target-entity']; | ||
| 444 | } | ||
| 445 | |||
| 446 | if (isset($associationIds[$mapping['fieldName']])) { | ||
| 447 | $mapping['id'] = true; | ||
| 448 | } | ||
| 449 | |||
| 450 | if (isset($manyToOneElement['fetch'])) { | ||
| 451 | $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToOneElement['fetch']); | ||
| 452 | } | ||
| 453 | |||
| 454 | if (isset($manyToOneElement['inversed-by'])) { | ||
| 455 | $mapping['inversedBy'] = (string) $manyToOneElement['inversed-by']; | ||
| 456 | } | ||
| 457 | |||
| 458 | $joinColumns = []; | ||
| 459 | |||
| 460 | if (isset($manyToOneElement->{'join-column'})) { | ||
| 461 | $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'}); | ||
| 462 | } elseif (isset($manyToOneElement->{'join-columns'})) { | ||
| 463 | foreach ($manyToOneElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 464 | $joinColumns[] = $this->joinColumnToArray($joinColumnElement); | ||
| 465 | } | ||
| 466 | } | ||
| 467 | |||
| 468 | $mapping['joinColumns'] = $joinColumns; | ||
| 469 | |||
| 470 | if (isset($manyToOneElement->cascade)) { | ||
| 471 | $mapping['cascade'] = $this->getCascadeMappings($manyToOneElement->cascade); | ||
| 472 | } | ||
| 473 | |||
| 474 | // Evaluate second level cache | ||
| 475 | if (isset($manyToOneElement->cache)) { | ||
| 476 | $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache)); | ||
| 477 | } | ||
| 478 | |||
| 479 | $metadata->mapManyToOne($mapping); | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | // Evaluate <many-to-many ...> mappings | ||
| 484 | if (isset($xmlRoot->{'many-to-many'})) { | ||
| 485 | foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) { | ||
| 486 | $mapping = [ | ||
| 487 | 'fieldName' => (string) $manyToManyElement['field'], | ||
| 488 | ]; | ||
| 489 | |||
| 490 | if (isset($manyToManyElement['target-entity'])) { | ||
| 491 | $mapping['targetEntity'] = (string) $manyToManyElement['target-entity']; | ||
| 492 | } | ||
| 493 | |||
| 494 | if (isset($manyToManyElement['fetch'])) { | ||
| 495 | $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToManyElement['fetch']); | ||
| 496 | } | ||
| 497 | |||
| 498 | if (isset($manyToManyElement['orphan-removal'])) { | ||
| 499 | $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']); | ||
| 500 | } | ||
| 501 | |||
| 502 | if (isset($manyToManyElement['mapped-by'])) { | ||
| 503 | $mapping['mappedBy'] = (string) $manyToManyElement['mapped-by']; | ||
| 504 | } elseif (isset($manyToManyElement->{'join-table'})) { | ||
| 505 | if (isset($manyToManyElement['inversed-by'])) { | ||
| 506 | $mapping['inversedBy'] = (string) $manyToManyElement['inversed-by']; | ||
| 507 | } | ||
| 508 | |||
| 509 | $joinTableElement = $manyToManyElement->{'join-table'}; | ||
| 510 | $joinTable = [ | ||
| 511 | 'name' => (string) $joinTableElement['name'], | ||
| 512 | ]; | ||
| 513 | |||
| 514 | if (isset($joinTableElement['schema'])) { | ||
| 515 | $joinTable['schema'] = (string) $joinTableElement['schema']; | ||
| 516 | } | ||
| 517 | |||
| 518 | if (isset($joinTableElement->options)) { | ||
| 519 | $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); | ||
| 520 | } | ||
| 521 | |||
| 522 | foreach ($joinTableElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 523 | $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); | ||
| 524 | } | ||
| 525 | |||
| 526 | foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 527 | $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); | ||
| 528 | } | ||
| 529 | |||
| 530 | $mapping['joinTable'] = $joinTable; | ||
| 531 | } | ||
| 532 | |||
| 533 | if (isset($manyToManyElement->cascade)) { | ||
| 534 | $mapping['cascade'] = $this->getCascadeMappings($manyToManyElement->cascade); | ||
| 535 | } | ||
| 536 | |||
| 537 | if (isset($manyToManyElement->{'order-by'})) { | ||
| 538 | $orderBy = []; | ||
| 539 | foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) { | ||
| 540 | /** @psalm-suppress DeprecatedConstant */ | ||
| 541 | $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) | ||
| 542 | ? (string) $orderByField['direction'] | ||
| 543 | : (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC); | ||
| 544 | } | ||
| 545 | |||
| 546 | $mapping['orderBy'] = $orderBy; | ||
| 547 | } | ||
| 548 | |||
| 549 | if (isset($manyToManyElement['index-by'])) { | ||
| 550 | $mapping['indexBy'] = (string) $manyToManyElement['index-by']; | ||
| 551 | } elseif (isset($manyToManyElement->{'index-by'})) { | ||
| 552 | throw new InvalidArgumentException('<index-by /> is not a valid tag'); | ||
| 553 | } | ||
| 554 | |||
| 555 | // Evaluate second level cache | ||
| 556 | if (isset($manyToManyElement->cache)) { | ||
| 557 | $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache)); | ||
| 558 | } | ||
| 559 | |||
| 560 | $metadata->mapManyToMany($mapping); | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | // Evaluate association-overrides | ||
| 565 | if (isset($xmlRoot->{'attribute-overrides'})) { | ||
| 566 | foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} ?? [] as $overrideElement) { | ||
| 567 | $fieldName = (string) $overrideElement['name']; | ||
| 568 | foreach ($overrideElement->field ?? [] as $field) { | ||
| 569 | $mapping = $this->columnToArray($field); | ||
| 570 | $mapping['fieldName'] = $fieldName; | ||
| 571 | $metadata->setAttributeOverride($fieldName, $mapping); | ||
| 572 | } | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | // Evaluate association-overrides | ||
| 577 | if (isset($xmlRoot->{'association-overrides'})) { | ||
| 578 | foreach ($xmlRoot->{'association-overrides'}->{'association-override'} ?? [] as $overrideElement) { | ||
| 579 | $fieldName = (string) $overrideElement['name']; | ||
| 580 | $override = []; | ||
| 581 | |||
| 582 | // Check for join-columns | ||
| 583 | if (isset($overrideElement->{'join-columns'})) { | ||
| 584 | $joinColumns = []; | ||
| 585 | foreach ($overrideElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 586 | $joinColumns[] = $this->joinColumnToArray($joinColumnElement); | ||
| 587 | } | ||
| 588 | |||
| 589 | $override['joinColumns'] = $joinColumns; | ||
| 590 | } | ||
| 591 | |||
| 592 | // Check for join-table | ||
| 593 | if ($overrideElement->{'join-table'}) { | ||
| 594 | $joinTable = null; | ||
| 595 | $joinTableElement = $overrideElement->{'join-table'}; | ||
| 596 | |||
| 597 | $joinTable = [ | ||
| 598 | 'name' => (string) $joinTableElement['name'], | ||
| 599 | 'schema' => (string) $joinTableElement['schema'], | ||
| 600 | ]; | ||
| 601 | |||
| 602 | if (isset($joinTableElement->options)) { | ||
| 603 | $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); | ||
| 604 | } | ||
| 605 | |||
| 606 | if (isset($joinTableElement->{'join-columns'})) { | ||
| 607 | foreach ($joinTableElement->{'join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 608 | $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | if (isset($joinTableElement->{'inverse-join-columns'})) { | ||
| 613 | foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} ?? [] as $joinColumnElement) { | ||
| 614 | $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); | ||
| 615 | } | ||
| 616 | } | ||
| 617 | |||
| 618 | $override['joinTable'] = $joinTable; | ||
| 619 | } | ||
| 620 | |||
| 621 | // Check for inversed-by | ||
| 622 | if (isset($overrideElement->{'inversed-by'})) { | ||
| 623 | $override['inversedBy'] = (string) $overrideElement->{'inversed-by'}['name']; | ||
| 624 | } | ||
| 625 | |||
| 626 | // Check for `fetch` | ||
| 627 | if (isset($overrideElement['fetch'])) { | ||
| 628 | $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . (string) $overrideElement['fetch']); | ||
| 629 | } | ||
| 630 | |||
| 631 | $metadata->setAssociationOverride($fieldName, $override); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 635 | // Evaluate <lifecycle-callbacks...> | ||
| 636 | if (isset($xmlRoot->{'lifecycle-callbacks'})) { | ||
| 637 | foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} ?? [] as $lifecycleCallback) { | ||
| 638 | $metadata->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string) $lifecycleCallback['type'])); | ||
| 639 | } | ||
| 640 | } | ||
| 641 | |||
| 642 | // Evaluate entity listener | ||
| 643 | if (isset($xmlRoot->{'entity-listeners'})) { | ||
| 644 | foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} ?? [] as $listenerElement) { | ||
| 645 | $className = (string) $listenerElement['class']; | ||
| 646 | // Evaluate the listener using naming convention. | ||
| 647 | if ($listenerElement->count() === 0) { | ||
| 648 | EntityListenerBuilder::bindEntityListener($metadata, $className); | ||
| 649 | |||
| 650 | continue; | ||
| 651 | } | ||
| 652 | |||
| 653 | foreach ($listenerElement as $callbackElement) { | ||
| 654 | $eventName = (string) $callbackElement['type']; | ||
| 655 | $methodName = (string) $callbackElement['method']; | ||
| 656 | |||
| 657 | $metadata->addEntityListener($eventName, $className, $methodName); | ||
| 658 | } | ||
| 659 | } | ||
| 660 | } | ||
| 661 | } | ||
| 662 | |||
| 663 | /** | ||
| 664 | * Parses (nested) option elements. | ||
| 665 | * | ||
| 666 | * @return mixed[] The options array. | ||
| 667 | * @psalm-return array<int|string, array<int|string, mixed|string>|bool|string> | ||
| 668 | */ | ||
| 669 | private function parseOptions(SimpleXMLElement|null $options): array | ||
| 670 | { | ||
| 671 | $array = []; | ||
| 672 | |||
| 673 | foreach ($options ?? [] as $option) { | ||
| 674 | if ($option->count()) { | ||
| 675 | $value = $this->parseOptions($option->children()); | ||
| 676 | } else { | ||
| 677 | $value = (string) $option; | ||
| 678 | } | ||
| 679 | |||
| 680 | $attributes = $option->attributes(); | ||
| 681 | |||
| 682 | if (isset($attributes->name)) { | ||
| 683 | $nameAttribute = (string) $attributes->name; | ||
| 684 | $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true) | ||
| 685 | ? $this->evaluateBoolean($value) | ||
| 686 | : $value; | ||
| 687 | } else { | ||
| 688 | $array[] = $value; | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | return $array; | ||
| 693 | } | ||
| 694 | |||
| 695 | /** | ||
| 696 | * Constructs a joinColumn mapping array based on the information | ||
| 697 | * found in the given SimpleXMLElement. | ||
| 698 | * | ||
| 699 | * @param SimpleXMLElement $joinColumnElement The XML element. | ||
| 700 | * | ||
| 701 | * @return mixed[] The mapping array. | ||
| 702 | * @psalm-return array{ | ||
| 703 | * name: string, | ||
| 704 | * referencedColumnName: string, | ||
| 705 | * unique?: bool, | ||
| 706 | * nullable?: bool, | ||
| 707 | * onDelete?: string, | ||
| 708 | * columnDefinition?: string, | ||
| 709 | * options?: mixed[] | ||
| 710 | * } | ||
| 711 | */ | ||
| 712 | private function joinColumnToArray(SimpleXMLElement $joinColumnElement): array | ||
| 713 | { | ||
| 714 | $joinColumn = [ | ||
| 715 | 'name' => (string) $joinColumnElement['name'], | ||
| 716 | 'referencedColumnName' => (string) $joinColumnElement['referenced-column-name'], | ||
| 717 | ]; | ||
| 718 | |||
| 719 | if (isset($joinColumnElement['unique'])) { | ||
| 720 | $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']); | ||
| 721 | } | ||
| 722 | |||
| 723 | if (isset($joinColumnElement['nullable'])) { | ||
| 724 | $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']); | ||
| 725 | } | ||
| 726 | |||
| 727 | if (isset($joinColumnElement['on-delete'])) { | ||
| 728 | $joinColumn['onDelete'] = (string) $joinColumnElement['on-delete']; | ||
| 729 | } | ||
| 730 | |||
| 731 | if (isset($joinColumnElement['column-definition'])) { | ||
| 732 | $joinColumn['columnDefinition'] = (string) $joinColumnElement['column-definition']; | ||
| 733 | } | ||
| 734 | |||
| 735 | if (isset($joinColumnElement['options'])) { | ||
| 736 | $joinColumn['options'] = $this->parseOptions($joinColumnElement['options'] ? $joinColumnElement['options']->children() : null); | ||
| 737 | } | ||
| 738 | |||
| 739 | return $joinColumn; | ||
| 740 | } | ||
| 741 | |||
| 742 | /** | ||
| 743 | * Parses the given field as array. | ||
| 744 | * | ||
| 745 | * @return mixed[] | ||
| 746 | * @psalm-return array{ | ||
| 747 | * fieldName: string, | ||
| 748 | * type?: string, | ||
| 749 | * columnName?: string, | ||
| 750 | * length?: int, | ||
| 751 | * precision?: int, | ||
| 752 | * scale?: int, | ||
| 753 | * unique?: bool, | ||
| 754 | * nullable?: bool, | ||
| 755 | * notInsertable?: bool, | ||
| 756 | * notUpdatable?: bool, | ||
| 757 | * enumType?: string, | ||
| 758 | * version?: bool, | ||
| 759 | * columnDefinition?: string, | ||
| 760 | * options?: array | ||
| 761 | * } | ||
| 762 | */ | ||
| 763 | private function columnToArray(SimpleXMLElement $fieldMapping): array | ||
| 764 | { | ||
| 765 | $mapping = [ | ||
| 766 | 'fieldName' => (string) $fieldMapping['name'], | ||
| 767 | ]; | ||
| 768 | |||
| 769 | if (isset($fieldMapping['type'])) { | ||
| 770 | $mapping['type'] = (string) $fieldMapping['type']; | ||
| 771 | } | ||
| 772 | |||
| 773 | if (isset($fieldMapping['column'])) { | ||
| 774 | $mapping['columnName'] = (string) $fieldMapping['column']; | ||
| 775 | } | ||
| 776 | |||
| 777 | if (isset($fieldMapping['length'])) { | ||
| 778 | $mapping['length'] = (int) $fieldMapping['length']; | ||
| 779 | } | ||
| 780 | |||
| 781 | if (isset($fieldMapping['precision'])) { | ||
| 782 | $mapping['precision'] = (int) $fieldMapping['precision']; | ||
| 783 | } | ||
| 784 | |||
| 785 | if (isset($fieldMapping['scale'])) { | ||
| 786 | $mapping['scale'] = (int) $fieldMapping['scale']; | ||
| 787 | } | ||
| 788 | |||
| 789 | if (isset($fieldMapping['unique'])) { | ||
| 790 | $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']); | ||
| 791 | } | ||
| 792 | |||
| 793 | if (isset($fieldMapping['nullable'])) { | ||
| 794 | $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); | ||
| 795 | } | ||
| 796 | |||
| 797 | if (isset($fieldMapping['insertable']) && ! $this->evaluateBoolean($fieldMapping['insertable'])) { | ||
| 798 | $mapping['notInsertable'] = true; | ||
| 799 | } | ||
| 800 | |||
| 801 | if (isset($fieldMapping['updatable']) && ! $this->evaluateBoolean($fieldMapping['updatable'])) { | ||
| 802 | $mapping['notUpdatable'] = true; | ||
| 803 | } | ||
| 804 | |||
| 805 | if (isset($fieldMapping['generated'])) { | ||
| 806 | $mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . (string) $fieldMapping['generated']); | ||
| 807 | } | ||
| 808 | |||
| 809 | if (isset($fieldMapping['version']) && $fieldMapping['version']) { | ||
| 810 | $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); | ||
| 811 | } | ||
| 812 | |||
| 813 | if (isset($fieldMapping['column-definition'])) { | ||
| 814 | $mapping['columnDefinition'] = (string) $fieldMapping['column-definition']; | ||
| 815 | } | ||
| 816 | |||
| 817 | if (isset($fieldMapping['enum-type'])) { | ||
| 818 | $mapping['enumType'] = (string) $fieldMapping['enum-type']; | ||
| 819 | } | ||
| 820 | |||
| 821 | if (isset($fieldMapping->options)) { | ||
| 822 | $mapping['options'] = $this->parseOptions($fieldMapping->options->children()); | ||
| 823 | } | ||
| 824 | |||
| 825 | return $mapping; | ||
| 826 | } | ||
| 827 | |||
| 828 | /** | ||
| 829 | * Parse / Normalize the cache configuration | ||
| 830 | * | ||
| 831 | * @return mixed[] | ||
| 832 | * @psalm-return array{usage: int|null, region?: string} | ||
| 833 | */ | ||
| 834 | private function cacheToArray(SimpleXMLElement $cacheMapping): array | ||
| 835 | { | ||
| 836 | $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null; | ||
| 837 | $usage = isset($cacheMapping['usage']) ? strtoupper((string) $cacheMapping['usage']) : null; | ||
| 838 | |||
| 839 | if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) { | ||
| 840 | throw new InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage)); | ||
| 841 | } | ||
| 842 | |||
| 843 | if ($usage) { | ||
| 844 | $usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage); | ||
| 845 | } | ||
| 846 | |||
| 847 | return [ | ||
| 848 | 'usage' => $usage, | ||
| 849 | 'region' => $region, | ||
| 850 | ]; | ||
| 851 | } | ||
| 852 | |||
| 853 | /** | ||
| 854 | * Gathers a list of cascade options found in the given cascade element. | ||
| 855 | * | ||
| 856 | * @param SimpleXMLElement $cascadeElement The cascade element. | ||
| 857 | * | ||
| 858 | * @return string[] The list of cascade options. | ||
| 859 | * @psalm-return list<string> | ||
| 860 | */ | ||
| 861 | private function getCascadeMappings(SimpleXMLElement $cascadeElement): array | ||
| 862 | { | ||
| 863 | $cascades = []; | ||
| 864 | $children = $cascadeElement->children(); | ||
| 865 | assert($children !== null); | ||
| 866 | |||
| 867 | foreach ($children as $action) { | ||
| 868 | // According to the JPA specifications, XML uses "cascade-persist" | ||
| 869 | // instead of "persist". Here, both variations | ||
| 870 | // are supported because Attribute uses "persist" | ||
| 871 | // and we want to make sure that this driver doesn't need to know | ||
| 872 | // anything about the supported cascading actions | ||
| 873 | $cascades[] = str_replace('cascade-', '', $action->getName()); | ||
| 874 | } | ||
| 875 | |||
| 876 | return $cascades; | ||
| 877 | } | ||
| 878 | |||
| 879 | /** | ||
| 880 | * {@inheritDoc} | ||
| 881 | */ | ||
| 882 | protected function loadMappingFile($file) | ||
| 883 | { | ||
| 884 | $this->validateMapping($file); | ||
| 885 | $result = []; | ||
| 886 | // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577 | ||
| 887 | $xmlElement = simplexml_load_string(file_get_contents($file)); | ||
| 888 | assert($xmlElement !== false); | ||
| 889 | |||
| 890 | if (isset($xmlElement->entity)) { | ||
| 891 | foreach ($xmlElement->entity as $entityElement) { | ||
| 892 | /** @psalm-var class-string $entityName */ | ||
| 893 | $entityName = (string) $entityElement['name']; | ||
| 894 | $result[$entityName] = $entityElement; | ||
| 895 | } | ||
| 896 | } elseif (isset($xmlElement->{'mapped-superclass'})) { | ||
| 897 | foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) { | ||
| 898 | /** @psalm-var class-string $className */ | ||
| 899 | $className = (string) $mappedSuperClass['name']; | ||
| 900 | $result[$className] = $mappedSuperClass; | ||
| 901 | } | ||
| 902 | } elseif (isset($xmlElement->embeddable)) { | ||
| 903 | foreach ($xmlElement->embeddable as $embeddableElement) { | ||
| 904 | /** @psalm-var class-string $embeddableName */ | ||
| 905 | $embeddableName = (string) $embeddableElement['name']; | ||
| 906 | $result[$embeddableName] = $embeddableElement; | ||
| 907 | } | ||
| 908 | } | ||
| 909 | |||
| 910 | return $result; | ||
| 911 | } | ||
| 912 | |||
| 913 | private function validateMapping(string $file): void | ||
| 914 | { | ||
| 915 | if (! $this->isXsdValidationEnabled) { | ||
| 916 | return; | ||
| 917 | } | ||
| 918 | |||
| 919 | $backedUpErrorSetting = libxml_use_internal_errors(true); | ||
| 920 | |||
| 921 | try { | ||
| 922 | $document = new DOMDocument(); | ||
| 923 | $document->load($file); | ||
| 924 | |||
| 925 | if (! $document->schemaValidate(__DIR__ . '/../../../doctrine-mapping.xsd')) { | ||
| 926 | throw MappingException::fromLibXmlErrors(libxml_get_errors()); | ||
| 927 | } | ||
| 928 | } finally { | ||
| 929 | libxml_clear_errors(); | ||
| 930 | libxml_use_internal_errors($backedUpErrorSetting); | ||
| 931 | } | ||
| 932 | } | ||
| 933 | |||
| 934 | protected function evaluateBoolean(mixed $element): bool | ||
| 935 | { | ||
| 936 | $flag = (string) $element; | ||
| 937 | |||
| 938 | return $flag === 'true' || $flag === '1'; | ||
| 939 | } | ||
| 940 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 10 | final class Embeddable implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/Embedded.php b/vendor/doctrine/orm/src/Mapping/Embedded.php new file mode 100644 index 0000000..be69b4f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Embedded.php | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class Embedded implements MappingAttribute | ||
| 11 | { | ||
| 12 | public function __construct( | ||
| 13 | public readonly string|null $class = null, | ||
| 14 | public readonly string|bool|null $columnPrefix = null, | ||
| 15 | ) { | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/EmbeddedClassMapping.php b/vendor/doctrine/orm/src/Mapping/EmbeddedClassMapping.php new file mode 100644 index 0000000..8fd02c9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/EmbeddedClassMapping.php | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use ArrayAccess; | ||
| 8 | |||
| 9 | use function property_exists; | ||
| 10 | |||
| 11 | /** @template-implements ArrayAccess<string, mixed> */ | ||
| 12 | final class EmbeddedClassMapping implements ArrayAccess | ||
| 13 | { | ||
| 14 | use ArrayAccessImplementation; | ||
| 15 | |||
| 16 | public string|false|null $columnPrefix = null; | ||
| 17 | public string|null $declaredField = null; | ||
| 18 | public string|null $originalField = null; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * This is set when this embedded-class field is inherited by this class | ||
| 22 | * from another (inheritance) parent <em>entity</em> class. The value is | ||
| 23 | * the FQCN of the topmost entity class that contains mapping information | ||
| 24 | * for this field. (If there are transient classes in the class hierarchy, | ||
| 25 | * these are ignored, so the class property may in fact come from a class | ||
| 26 | * further up in the PHP class hierarchy.) Fields initially declared in | ||
| 27 | * mapped superclasses are <em>not</em> considered 'inherited' in the | ||
| 28 | * nearest entity subclasses. | ||
| 29 | * | ||
| 30 | * @var class-string|null | ||
| 31 | */ | ||
| 32 | public string|null $inherited = null; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * This is set when the embedded-class field does not appear for the first | ||
| 36 | * time in this class, but is originally declared in another parent | ||
| 37 | * <em>entity or mapped superclass</em>. The value is the FQCN of the | ||
| 38 | * topmost non-transient class that contains mapping information for this | ||
| 39 | * field. | ||
| 40 | * | ||
| 41 | * @var class-string|null | ||
| 42 | */ | ||
| 43 | public string|null $declared = null; | ||
| 44 | |||
| 45 | /** @param class-string $class */ | ||
| 46 | public function __construct(public string $class) | ||
| 47 | { | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * @psalm-param array{ | ||
| 52 | * class: class-string, | ||
| 53 | * columnPrefix?: false|string|null, | ||
| 54 | * declaredField?: string|null, | ||
| 55 | * originalField?: string|null, | ||
| 56 | * inherited?: class-string|null, | ||
| 57 | * declared?: class-string|null, | ||
| 58 | * } $mappingArray | ||
| 59 | */ | ||
| 60 | public static function fromMappingArray(array $mappingArray): self | ||
| 61 | { | ||
| 62 | $mapping = new self($mappingArray['class']); | ||
| 63 | foreach ($mappingArray as $key => $value) { | ||
| 64 | if ($key === 'class') { | ||
| 65 | continue; | ||
| 66 | } | ||
| 67 | |||
| 68 | if (property_exists($mapping, $key)) { | ||
| 69 | $mapping->$key = $value; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | return $mapping; | ||
| 74 | } | ||
| 75 | |||
| 76 | /** @return list<string> */ | ||
| 77 | public function __sleep(): array | ||
| 78 | { | ||
| 79 | $serialized = ['class']; | ||
| 80 | |||
| 81 | if ($this->columnPrefix) { | ||
| 82 | $serialized[] = 'columnPrefix'; | ||
| 83 | } | ||
| 84 | |||
| 85 | foreach (['declaredField', 'originalField', 'inherited', 'declared'] as $property) { | ||
| 86 | if ($this->$property !== null) { | ||
| 87 | $serialized[] = $property; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | return $serialized; | ||
| 92 | } | ||
| 93 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | use Doctrine\ORM\EntityRepository; | ||
| 9 | |||
| 10 | /** @template T of object */ | ||
| 11 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 12 | final class Entity implements MappingAttribute | ||
| 13 | { | ||
| 14 | /** @psalm-param class-string<EntityRepository<T>>|null $repositoryClass */ | ||
| 15 | public function __construct( | ||
| 16 | public readonly string|null $repositoryClass = null, | ||
| 17 | public readonly bool $readOnly = false, | ||
| 18 | ) { | ||
| 19 | } | ||
| 20 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * A resolver is used to instantiate an entity listener. | ||
| 9 | */ | ||
| 10 | interface EntityListenerResolver | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * Clear all instances from the set, or a specific instance when given its identifier. | ||
| 14 | * | ||
| 15 | * @param string|null $className May be any arbitrary string. Name kept for BC only. | ||
| 16 | */ | ||
| 17 | public function clear(string|null $className = null): void; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Returns a entity listener instance for the given identifier. | ||
| 21 | * | ||
| 22 | * @param string $className May be any arbitrary string. Name kept for BC only. | ||
| 23 | */ | ||
| 24 | public function resolve(string $className): object; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Register a entity listener instance. | ||
| 28 | */ | ||
| 29 | public function register(object $object): void; | ||
| 30 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/EntityListeners.php b/vendor/doctrine/orm/src/Mapping/EntityListeners.php new file mode 100644 index 0000000..8f822ea --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/EntityListeners.php | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * The EntityListeners attribute specifies the callback listener classes to be used for an entity or mapped superclass. | ||
| 11 | * The EntityListeners attribute may be applied to an entity class or mapped superclass. | ||
| 12 | */ | ||
| 13 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 14 | final class EntityListeners implements MappingAttribute | ||
| 15 | { | ||
| 16 | /** @param array<string> $value */ | ||
| 17 | public function __construct( | ||
| 18 | public readonly array $value = [], | ||
| 19 | ) { | ||
| 20 | } | ||
| 21 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Exception; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Exception\ORMException; | ||
| 8 | use LogicException; | ||
| 9 | |||
| 10 | use function sprintf; | ||
| 11 | use function var_export; | ||
| 12 | |||
| 13 | final class InvalidCustomGenerator extends LogicException implements ORMException | ||
| 14 | { | ||
| 15 | public static function onClassNotConfigured(): self | ||
| 16 | { | ||
| 17 | return new self('Cannot instantiate custom generator, no class has been defined'); | ||
| 18 | } | ||
| 19 | |||
| 20 | /** @param mixed[] $definition */ | ||
| 21 | public static function onMissingClass(array $definition): self | ||
| 22 | { | ||
| 23 | return new self(sprintf( | ||
| 24 | 'Cannot instantiate custom generator : %s', | ||
| 25 | var_export($definition, true), | ||
| 26 | )); | ||
| 27 | } | ||
| 28 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/Exception/UnknownGeneratorType.php b/vendor/doctrine/orm/src/Mapping/Exception/UnknownGeneratorType.php new file mode 100644 index 0000000..c8970bf --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Exception/UnknownGeneratorType.php | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping\Exception; | ||
| 6 | |||
| 7 | use Doctrine\ORM\Exception\ORMException; | ||
| 8 | use LogicException; | ||
| 9 | |||
| 10 | final class UnknownGeneratorType extends LogicException implements ORMException | ||
| 11 | { | ||
| 12 | public static function create(int $generatorType): self | ||
| 13 | { | ||
| 14 | return new self('Unknown generator type: ' . $generatorType); | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/FieldMapping.php b/vendor/doctrine/orm/src/Mapping/FieldMapping.php new file mode 100644 index 0000000..4c09196 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/FieldMapping.php | |||
| @@ -0,0 +1,169 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use ArrayAccess; | ||
| 8 | use BackedEnum; | ||
| 9 | |||
| 10 | use function in_array; | ||
| 11 | use function property_exists; | ||
| 12 | |||
| 13 | /** @template-implements ArrayAccess<string, mixed> */ | ||
| 14 | final class FieldMapping implements ArrayAccess | ||
| 15 | { | ||
| 16 | use ArrayAccessImplementation; | ||
| 17 | |||
| 18 | /** The database length of the column. Optional. Default value taken from the type. */ | ||
| 19 | public int|null $length = null; | ||
| 20 | /** | ||
| 21 | * Marks the field as the primary key of the entity. Multiple | ||
| 22 | * fields of an entity can have the id attribute, forming a composite key. | ||
| 23 | */ | ||
| 24 | public bool|null $id = null; | ||
| 25 | public bool|null $nullable = null; | ||
| 26 | public bool|null $notInsertable = null; | ||
| 27 | public bool|null $notUpdatable = null; | ||
| 28 | public string|null $columnDefinition = null; | ||
| 29 | /** @psalm-var ClassMetadata::GENERATED_*|null */ | ||
| 30 | public int|null $generated = null; | ||
| 31 | /** @var class-string<BackedEnum>|null */ | ||
| 32 | public string|null $enumType = null; | ||
| 33 | /** | ||
| 34 | * The precision of a decimal column. | ||
| 35 | * Only valid if the column type is decimal | ||
| 36 | */ | ||
| 37 | public int|null $precision = null; | ||
| 38 | /** | ||
| 39 | * The scale of a decimal column. | ||
| 40 | * Only valid if the column type is decimal | ||
| 41 | */ | ||
| 42 | public int|null $scale = null; | ||
| 43 | /** Whether a unique constraint should be generated for the column. */ | ||
| 44 | public bool|null $unique = null; | ||
| 45 | /** | ||
| 46 | * @var class-string|null This is set when the field is inherited by this | ||
| 47 | * class from another (inheritance) parent <em>entity</em> class. The value | ||
| 48 | * is the FQCN of the topmost entity class that contains mapping information | ||
| 49 | * for this field. (If there are transient classes in the class hierarchy, | ||
| 50 | * these are ignored, so the class property may in fact come from a class | ||
| 51 | * further up in the PHP class hierarchy.) | ||
| 52 | * Fields initially declared in mapped superclasses are | ||
| 53 | * <em>not</em> considered 'inherited' in the nearest entity subclasses. | ||
| 54 | */ | ||
| 55 | public string|null $inherited = null; | ||
| 56 | |||
| 57 | public string|null $originalClass = null; | ||
| 58 | public string|null $originalField = null; | ||
| 59 | public bool|null $quoted = null; | ||
| 60 | /** | ||
| 61 | * @var class-string|null This is set when the field does not appear for | ||
| 62 | * the first time in this class, but is originally declared in another | ||
| 63 | * parent <em>entity or mapped superclass</em>. The value is the FQCN of | ||
| 64 | * the topmost non-transient class that contains mapping information for | ||
| 65 | * this field. | ||
| 66 | */ | ||
| 67 | public string|null $declared = null; | ||
| 68 | public string|null $declaredField = null; | ||
| 69 | public array|null $options = null; | ||
| 70 | public bool|null $version = null; | ||
| 71 | public string|int|null $default = null; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * @param string $type The type name of the mapped field. Can be one of | ||
| 75 | * Doctrine's mapping types or a custom mapping type. | ||
| 76 | * @param string $fieldName The name of the field in the Entity. | ||
| 77 | * @param string $columnName The column name. Optional. Defaults to the field name. | ||
| 78 | */ | ||
| 79 | public function __construct( | ||
| 80 | public string $type, | ||
| 81 | public string $fieldName, | ||
| 82 | public string $columnName, | ||
| 83 | ) { | ||
| 84 | } | ||
| 85 | |||
| 86 | /** | ||
| 87 | * @param array<string, mixed> $mappingArray | ||
| 88 | * @psalm-param array{ | ||
| 89 | * type: string, | ||
| 90 | * fieldName: string, | ||
| 91 | * columnName: string, | ||
| 92 | * length?: int|null, | ||
| 93 | * id?: bool|null, | ||
| 94 | * nullable?: bool|null, | ||
| 95 | * notInsertable?: bool|null, | ||
| 96 | * notUpdatable?: bool|null, | ||
| 97 | * columnDefinition?: string|null, | ||
| 98 | * generated?: ClassMetadata::GENERATED_*|null, | ||
| 99 | * enumType?: string|null, | ||
| 100 | * precision?: int|null, | ||
| 101 | * scale?: int|null, | ||
| 102 | * unique?: bool|null, | ||
| 103 | * inherited?: string|null, | ||
| 104 | * originalClass?: string|null, | ||
| 105 | * originalField?: string|null, | ||
| 106 | * quoted?: bool|null, | ||
| 107 | * declared?: string|null, | ||
| 108 | * declaredField?: string|null, | ||
| 109 | * options?: array<string, mixed>|null, | ||
| 110 | * version?: bool|null, | ||
| 111 | * default?: string|int|null, | ||
| 112 | * } $mappingArray | ||
| 113 | */ | ||
| 114 | public static function fromMappingArray(array $mappingArray): self | ||
| 115 | { | ||
| 116 | $mapping = new self( | ||
| 117 | $mappingArray['type'], | ||
| 118 | $mappingArray['fieldName'], | ||
| 119 | $mappingArray['columnName'], | ||
| 120 | ); | ||
| 121 | foreach ($mappingArray as $key => $value) { | ||
| 122 | if (in_array($key, ['type', 'fieldName', 'columnName'])) { | ||
| 123 | continue; | ||
| 124 | } | ||
| 125 | |||
| 126 | if (property_exists($mapping, $key)) { | ||
| 127 | $mapping->$key = $value; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | return $mapping; | ||
| 132 | } | ||
| 133 | |||
| 134 | /** @return list<string> */ | ||
| 135 | public function __sleep(): array | ||
| 136 | { | ||
| 137 | $serialized = ['type', 'fieldName', 'columnName']; | ||
| 138 | |||
| 139 | foreach (['nullable', 'notInsertable', 'notUpdatable', 'id', 'unique', 'version', 'quoted'] as $boolKey) { | ||
| 140 | if ($this->$boolKey) { | ||
| 141 | $serialized[] = $boolKey; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | foreach ( | ||
| 146 | [ | ||
| 147 | 'length', | ||
| 148 | 'columnDefinition', | ||
| 149 | 'generated', | ||
| 150 | 'enumType', | ||
| 151 | 'precision', | ||
| 152 | 'scale', | ||
| 153 | 'inherited', | ||
| 154 | 'originalClass', | ||
| 155 | 'originalField', | ||
| 156 | 'declared', | ||
| 157 | 'declaredField', | ||
| 158 | 'options', | ||
| 159 | 'default', | ||
| 160 | ] as $key | ||
| 161 | ) { | ||
| 162 | if ($this->$key !== null) { | ||
| 163 | $serialized[] = $key; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | return $serialized; | ||
| 168 | } | ||
| 169 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class GeneratedValue implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** @psalm-param 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM' $strategy */ | ||
| 13 | public function __construct( | ||
| 14 | public readonly string $strategy = 'AUTO', | ||
| 15 | ) { | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/HasLifecycleCallbacks.php b/vendor/doctrine/orm/src/Mapping/HasLifecycleCallbacks.php new file mode 100644 index 0000000..d41a696 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/HasLifecycleCallbacks.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 10 | final class HasLifecycleCallbacks implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/Id.php b/vendor/doctrine/orm/src/Mapping/Id.php new file mode 100644 index 0000000..e85dd2f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Id.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class Id implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/Index.php b/vendor/doctrine/orm/src/Mapping/Index.php new file mode 100644 index 0000000..1de939e --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Index.php | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] | ||
| 10 | final class Index implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * @param array<string>|null $columns | ||
| 14 | * @param array<string>|null $fields | ||
| 15 | * @param array<string>|null $flags | ||
| 16 | * @param array<string,mixed>|null $options | ||
| 17 | */ | ||
| 18 | public function __construct( | ||
| 19 | public readonly string|null $name = null, | ||
| 20 | public readonly array|null $columns = null, | ||
| 21 | public readonly array|null $fields = null, | ||
| 22 | public readonly array|null $flags = null, | ||
| 23 | public readonly array|null $options = null, | ||
| 24 | ) { | ||
| 25 | } | ||
| 26 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 10 | final class InheritanceType implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** @psalm-param 'NONE'|'JOINED'|'SINGLE_TABLE' $value */ | ||
| 13 | public function __construct( | ||
| 14 | public readonly string $value, | ||
| 15 | ) { | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/InverseJoinColumn.php b/vendor/doctrine/orm/src/Mapping/InverseJoinColumn.php new file mode 100644 index 0000000..2a77f3f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/InverseJoinColumn.php | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] | ||
| 10 | final class InverseJoinColumn implements MappingAttribute | ||
| 11 | { | ||
| 12 | use JoinColumnProperties; | ||
| 13 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/InverseSideMapping.php b/vendor/doctrine/orm/src/Mapping/InverseSideMapping.php new file mode 100644 index 0000000..56dce9f --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/InverseSideMapping.php | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | abstract class InverseSideMapping extends AssociationMapping | ||
| 8 | { | ||
| 9 | /** | ||
| 10 | * required for bidirectional associations | ||
| 11 | * The name of the field that completes the bidirectional association on | ||
| 12 | * the owning side. This key must be specified on the inverse side of a | ||
| 13 | * bidirectional association. | ||
| 14 | */ | ||
| 15 | public string $mappedBy; | ||
| 16 | |||
| 17 | final public function backRefFieldName(): string | ||
| 18 | { | ||
| 19 | return $this->mappedBy; | ||
| 20 | } | ||
| 21 | |||
| 22 | /** @return list<string> */ | ||
| 23 | public function __sleep(): array | ||
| 24 | { | ||
| 25 | return [ | ||
| 26 | ...parent::__sleep(), | ||
| 27 | 'mappedBy', | ||
| 28 | ]; | ||
| 29 | } | ||
| 30 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] | ||
| 10 | final class JoinColumn implements MappingAttribute | ||
| 11 | { | ||
| 12 | use JoinColumnProperties; | ||
| 13 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/JoinColumnMapping.php b/vendor/doctrine/orm/src/Mapping/JoinColumnMapping.php new file mode 100644 index 0000000..172c256 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/JoinColumnMapping.php | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use ArrayAccess; | ||
| 8 | |||
| 9 | use function property_exists; | ||
| 10 | |||
| 11 | /** @template-implements ArrayAccess<string, mixed> */ | ||
| 12 | final class JoinColumnMapping implements ArrayAccess | ||
| 13 | { | ||
| 14 | use ArrayAccessImplementation; | ||
| 15 | |||
| 16 | public bool|null $unique = null; | ||
| 17 | public bool|null $quoted = null; | ||
| 18 | public string|null $fieldName = null; | ||
| 19 | public string|null $onDelete = null; | ||
| 20 | public string|null $columnDefinition = null; | ||
| 21 | public bool|null $nullable = null; | ||
| 22 | |||
| 23 | /** @var array<string, mixed>|null */ | ||
| 24 | public array|null $options = null; | ||
| 25 | |||
| 26 | public function __construct( | ||
| 27 | public string $name, | ||
| 28 | public string $referencedColumnName, | ||
| 29 | ) { | ||
| 30 | } | ||
| 31 | |||
| 32 | /** | ||
| 33 | * @param array<string, mixed> $mappingArray | ||
| 34 | * @psalm-param array{ | ||
| 35 | * name: string, | ||
| 36 | * referencedColumnName: string, | ||
| 37 | * unique?: bool|null, | ||
| 38 | * quoted?: bool|null, | ||
| 39 | * fieldName?: string|null, | ||
| 40 | * onDelete?: string|null, | ||
| 41 | * columnDefinition?: string|null, | ||
| 42 | * nullable?: bool|null, | ||
| 43 | * options?: array<string, mixed>|null, | ||
| 44 | * } $mappingArray | ||
| 45 | */ | ||
| 46 | public static function fromMappingArray(array $mappingArray): self | ||
| 47 | { | ||
| 48 | $mapping = new self($mappingArray['name'], $mappingArray['referencedColumnName']); | ||
| 49 | foreach ($mappingArray as $key => $value) { | ||
| 50 | if (property_exists($mapping, $key) && $value !== null) { | ||
| 51 | $mapping->$key = $value; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | return $mapping; | ||
| 56 | } | ||
| 57 | |||
| 58 | /** @return list<string> */ | ||
| 59 | public function __sleep(): array | ||
| 60 | { | ||
| 61 | $serialized = []; | ||
| 62 | |||
| 63 | foreach (['name', 'fieldName', 'onDelete', 'columnDefinition', 'referencedColumnName', 'options'] as $stringOrArrayKey) { | ||
| 64 | if ($this->$stringOrArrayKey !== null) { | ||
| 65 | $serialized[] = $stringOrArrayKey; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | foreach (['unique', 'quoted', 'nullable'] as $boolKey) { | ||
| 70 | if ($this->$boolKey !== null) { | ||
| 71 | $serialized[] = $boolKey; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | return $serialized; | ||
| 76 | } | ||
| 77 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | trait JoinColumnProperties | ||
| 8 | { | ||
| 9 | /** @param array<string, mixed> $options */ | ||
| 10 | public function __construct( | ||
| 11 | public readonly string|null $name = null, | ||
| 12 | public readonly string $referencedColumnName = 'id', | ||
| 13 | public readonly bool $unique = false, | ||
| 14 | public readonly bool $nullable = true, | ||
| 15 | public readonly mixed $onDelete = null, | ||
| 16 | public readonly string|null $columnDefinition = null, | ||
| 17 | public readonly string|null $fieldName = null, | ||
| 18 | public readonly array $options = [], | ||
| 19 | ) { | ||
| 20 | } | ||
| 21 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | final class JoinColumns implements MappingAttribute | ||
| 8 | { | ||
| 9 | /** @param array<JoinColumn> $value */ | ||
| 10 | public function __construct( | ||
| 11 | public readonly array $value, | ||
| 12 | ) { | ||
| 13 | } | ||
| 14 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class JoinTable implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** @var array<JoinColumn> */ | ||
| 13 | public readonly array $joinColumns; | ||
| 14 | |||
| 15 | /** @var array<JoinColumn> */ | ||
| 16 | public readonly array $inverseJoinColumns; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * @param array<JoinColumn>|JoinColumn $joinColumns | ||
| 20 | * @param array<JoinColumn>|JoinColumn $inverseJoinColumns | ||
| 21 | * @param array<string, mixed> $options | ||
| 22 | */ | ||
| 23 | public function __construct( | ||
| 24 | public readonly string|null $name = null, | ||
| 25 | public readonly string|null $schema = null, | ||
| 26 | array|JoinColumn $joinColumns = [], | ||
| 27 | array|JoinColumn $inverseJoinColumns = [], | ||
| 28 | public readonly array $options = [], | ||
| 29 | ) { | ||
| 30 | $this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns; | ||
| 31 | $this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn | ||
| 32 | ? [$inverseJoinColumns] | ||
| 33 | : $inverseJoinColumns; | ||
| 34 | } | ||
| 35 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use ArrayAccess; | ||
| 8 | |||
| 9 | use function array_map; | ||
| 10 | use function in_array; | ||
| 11 | |||
| 12 | /** @template-implements ArrayAccess<string, mixed> */ | ||
| 13 | final class JoinTableMapping implements ArrayAccess | ||
| 14 | { | ||
| 15 | use ArrayAccessImplementation; | ||
| 16 | |||
| 17 | public bool|null $quoted = null; | ||
| 18 | |||
| 19 | /** @var list<JoinColumnMapping> */ | ||
| 20 | public array $joinColumns = []; | ||
| 21 | |||
| 22 | /** @var list<JoinColumnMapping> */ | ||
| 23 | public array $inverseJoinColumns = []; | ||
| 24 | |||
| 25 | /** @var array<string, mixed> */ | ||
| 26 | public array $options = []; | ||
| 27 | |||
| 28 | public string|null $schema = null; | ||
| 29 | |||
| 30 | public function __construct(public string $name) | ||
| 31 | { | ||
| 32 | } | ||
| 33 | |||
| 34 | /** | ||
| 35 | * @param mixed[] $mappingArray | ||
| 36 | * @psalm-param array{ | ||
| 37 | * name: string, | ||
| 38 | * quoted?: bool|null, | ||
| 39 | * joinColumns?: mixed[], | ||
| 40 | * inverseJoinColumns?: mixed[], | ||
| 41 | * schema?: string|null, | ||
| 42 | * options?: array<string, mixed> | ||
| 43 | * } $mappingArray | ||
| 44 | */ | ||
| 45 | public static function fromMappingArray(array $mappingArray): self | ||
| 46 | { | ||
| 47 | $mapping = new self($mappingArray['name']); | ||
| 48 | |||
| 49 | foreach (['quoted', 'schema', 'options'] as $key) { | ||
| 50 | if (isset($mappingArray[$key])) { | ||
| 51 | $mapping->$key = $mappingArray[$key]; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | if (isset($mappingArray['joinColumns'])) { | ||
| 56 | foreach ($mappingArray['joinColumns'] as $column) { | ||
| 57 | $mapping->joinColumns[] = JoinColumnMapping::fromMappingArray($column); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | if (isset($mappingArray['inverseJoinColumns'])) { | ||
| 62 | foreach ($mappingArray['inverseJoinColumns'] as $column) { | ||
| 63 | $mapping->inverseJoinColumns[] = JoinColumnMapping::fromMappingArray($column); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | return $mapping; | ||
| 68 | } | ||
| 69 | |||
| 70 | public function offsetSet(mixed $offset, mixed $value): void | ||
| 71 | { | ||
| 72 | if (in_array($offset, ['joinColumns', 'inverseJoinColumns'], true)) { | ||
| 73 | $joinColumns = []; | ||
| 74 | foreach ($value as $column) { | ||
| 75 | $joinColumns[] = JoinColumnMapping::fromMappingArray($column); | ||
| 76 | } | ||
| 77 | |||
| 78 | $value = $joinColumns; | ||
| 79 | } | ||
| 80 | |||
| 81 | $this->$offset = $value; | ||
| 82 | } | ||
| 83 | |||
| 84 | /** @return mixed[] */ | ||
| 85 | public function toArray(): array | ||
| 86 | { | ||
| 87 | $array = (array) $this; | ||
| 88 | |||
| 89 | $toArray = static fn (JoinColumnMapping $column): array => (array) $column; | ||
| 90 | $array['joinColumns'] = array_map($toArray, $array['joinColumns']); | ||
| 91 | $array['inverseJoinColumns'] = array_map($toArray, $array['inverseJoinColumns']); | ||
| 92 | |||
| 93 | return $array; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** @return list<string> */ | ||
| 97 | public function __sleep(): array | ||
| 98 | { | ||
| 99 | $serialized = []; | ||
| 100 | |||
| 101 | foreach (['joinColumns', 'inverseJoinColumns', 'name', 'schema', 'options'] as $stringOrArrayKey) { | ||
| 102 | if ($this->$stringOrArrayKey !== null) { | ||
| 103 | $serialized[] = $stringOrArrayKey; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | foreach (['quoted'] as $boolKey) { | ||
| 108 | if ($this->$boolKey) { | ||
| 109 | $serialized[] = $boolKey; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | return $serialized; | ||
| 114 | } | ||
| 115 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class ManyToMany implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * @param class-string $targetEntity | ||
| 14 | * @param string[]|null $cascade | ||
| 15 | * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch | ||
| 16 | */ | ||
| 17 | public function __construct( | ||
| 18 | public readonly string $targetEntity, | ||
| 19 | public readonly string|null $mappedBy = null, | ||
| 20 | public readonly string|null $inversedBy = null, | ||
| 21 | public readonly array|null $cascade = null, | ||
| 22 | public readonly string $fetch = 'LAZY', | ||
| 23 | public readonly bool $orphanRemoval = false, | ||
| 24 | public readonly string|null $indexBy = null, | ||
| 25 | ) { | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ManyToManyAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/ManyToManyAssociationMapping.php new file mode 100644 index 0000000..8d963c2 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToManyAssociationMapping.php | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | interface ManyToManyAssociationMapping extends ToManyAssociationMapping | ||
| 8 | { | ||
| 9 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ManyToManyInverseSideMapping.php b/vendor/doctrine/orm/src/Mapping/ManyToManyInverseSideMapping.php new file mode 100644 index 0000000..8cd5cbd --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToManyInverseSideMapping.php | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | final class ManyToManyInverseSideMapping extends ToManyInverseSideMapping implements ManyToManyAssociationMapping | ||
| 8 | { | ||
| 9 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ManyToManyOwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/ManyToManyOwningSideMapping.php new file mode 100644 index 0000000..b09d56c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToManyOwningSideMapping.php | |||
| @@ -0,0 +1,185 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use function strtolower; | ||
| 8 | use function trim; | ||
| 9 | |||
| 10 | final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implements ManyToManyAssociationMapping | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * Specification of the join table and its join columns (foreign keys). | ||
| 14 | * Only valid for many-to-many mappings. Note that one-to-many associations | ||
| 15 | * can be mapped through a join table by simply mapping the association as | ||
| 16 | * many-to-many with a unique constraint on the join table. | ||
| 17 | */ | ||
| 18 | public JoinTableMapping $joinTable; | ||
| 19 | |||
| 20 | /** @var list<mixed> */ | ||
| 21 | public array $joinTableColumns = []; | ||
| 22 | |||
| 23 | /** @var array<string, string> */ | ||
| 24 | public array $relationToSourceKeyColumns = []; | ||
| 25 | /** @var array<string, string> */ | ||
| 26 | public array $relationToTargetKeyColumns = []; | ||
| 27 | |||
| 28 | /** @return array<string, mixed> */ | ||
| 29 | public function toArray(): array | ||
| 30 | { | ||
| 31 | $array = parent::toArray(); | ||
| 32 | |||
| 33 | $array['joinTable'] = $this->joinTable->toArray(); | ||
| 34 | |||
| 35 | return $array; | ||
| 36 | } | ||
| 37 | |||
| 38 | /** | ||
| 39 | * @param mixed[] $mappingArray | ||
| 40 | * @psalm-param array{ | ||
| 41 | * fieldName: string, | ||
| 42 | * sourceEntity: class-string, | ||
| 43 | * targetEntity: class-string, | ||
| 44 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 45 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 46 | * inherited?: class-string|null, | ||
| 47 | * declared?: class-string|null, | ||
| 48 | * cache?: array<mixed>|null, | ||
| 49 | * id?: bool|null, | ||
| 50 | * isOnDeleteCascade?: bool|null, | ||
| 51 | * originalClass?: class-string|null, | ||
| 52 | * originalField?: string|null, | ||
| 53 | * orphanRemoval?: bool, | ||
| 54 | * unique?: bool|null, | ||
| 55 | * joinTable?: mixed[]|null, | ||
| 56 | * type?: int, | ||
| 57 | * isOwningSide: bool, | ||
| 58 | * } $mappingArray | ||
| 59 | */ | ||
| 60 | public static function fromMappingArrayAndNamingStrategy(array $mappingArray, NamingStrategy $namingStrategy): self | ||
| 61 | { | ||
| 62 | if (isset($mappingArray['joinTable']['joinColumns'])) { | ||
| 63 | foreach ($mappingArray['joinTable']['joinColumns'] as $key => $joinColumn) { | ||
| 64 | if (empty($joinColumn['name'])) { | ||
| 65 | $mappingArray['joinTable']['joinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( | ||
| 66 | $mappingArray['sourceEntity'], | ||
| 67 | $joinColumn['referencedColumnName'] ?? null, | ||
| 68 | ); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | if (isset($mappingArray['joinTable']['inverseJoinColumns'])) { | ||
| 74 | foreach ($mappingArray['joinTable']['inverseJoinColumns'] as $key => $joinColumn) { | ||
| 75 | if (empty($joinColumn['name'])) { | ||
| 76 | $mappingArray['joinTable']['inverseJoinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( | ||
| 77 | $mappingArray['targetEntity'], | ||
| 78 | $joinColumn['referencedColumnName'] ?? null, | ||
| 79 | ); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | // owning side MUST have a join table | ||
| 85 | if (! isset($mappingArray['joinTable']) || ! isset($mappingArray['joinTable']['name'])) { | ||
| 86 | $mappingArray['joinTable']['name'] = $namingStrategy->joinTableName( | ||
| 87 | $mappingArray['sourceEntity'], | ||
| 88 | $mappingArray['targetEntity'], | ||
| 89 | $mappingArray['fieldName'], | ||
| 90 | ); | ||
| 91 | } | ||
| 92 | |||
| 93 | $mapping = parent::fromMappingArray($mappingArray); | ||
| 94 | |||
| 95 | $selfReferencingEntityWithoutJoinColumns = $mapping->sourceEntity === $mapping->targetEntity | ||
| 96 | && $mapping->joinTable->joinColumns === [] | ||
| 97 | && $mapping->joinTable->inverseJoinColumns === []; | ||
| 98 | |||
| 99 | if ($mapping->joinTable->joinColumns === []) { | ||
| 100 | $mapping->joinTable->joinColumns = [ | ||
| 101 | JoinColumnMapping::fromMappingArray([ | ||
| 102 | 'name' => $namingStrategy->joinKeyColumnName($mapping->sourceEntity, $selfReferencingEntityWithoutJoinColumns ? 'source' : null), | ||
| 103 | 'referencedColumnName' => $namingStrategy->referenceColumnName(), | ||
| 104 | 'onDelete' => 'CASCADE', | ||
| 105 | ]), | ||
| 106 | ]; | ||
| 107 | } | ||
| 108 | |||
| 109 | if ($mapping->joinTable->inverseJoinColumns === []) { | ||
| 110 | $mapping->joinTable->inverseJoinColumns = [ | ||
| 111 | JoinColumnMapping::fromMappingArray([ | ||
| 112 | 'name' => $namingStrategy->joinKeyColumnName($mapping->targetEntity, $selfReferencingEntityWithoutJoinColumns ? 'target' : null), | ||
| 113 | 'referencedColumnName' => $namingStrategy->referenceColumnName(), | ||
| 114 | 'onDelete' => 'CASCADE', | ||
| 115 | ]), | ||
| 116 | ]; | ||
| 117 | } | ||
| 118 | |||
| 119 | $mapping->joinTableColumns = []; | ||
| 120 | |||
| 121 | foreach ($mapping->joinTable->joinColumns as $joinColumn) { | ||
| 122 | if (empty($joinColumn->referencedColumnName)) { | ||
| 123 | $joinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); | ||
| 124 | } | ||
| 125 | |||
| 126 | if ($joinColumn->name[0] === '`') { | ||
| 127 | $joinColumn->name = trim($joinColumn->name, '`'); | ||
| 128 | $joinColumn->quoted = true; | ||
| 129 | } | ||
| 130 | |||
| 131 | if ($joinColumn->referencedColumnName[0] === '`') { | ||
| 132 | $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`'); | ||
| 133 | $joinColumn->quoted = true; | ||
| 134 | } | ||
| 135 | |||
| 136 | if (isset($joinColumn->onDelete) && strtolower($joinColumn->onDelete) === 'cascade') { | ||
| 137 | $mapping->isOnDeleteCascade = true; | ||
| 138 | } | ||
| 139 | |||
| 140 | $mapping->relationToSourceKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName; | ||
| 141 | $mapping->joinTableColumns[] = $joinColumn->name; | ||
| 142 | } | ||
| 143 | |||
| 144 | foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) { | ||
| 145 | if (empty($inverseJoinColumn->referencedColumnName)) { | ||
| 146 | $inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); | ||
| 147 | } | ||
| 148 | |||
| 149 | if ($inverseJoinColumn->name[0] === '`') { | ||
| 150 | $inverseJoinColumn->name = trim($inverseJoinColumn->name, '`'); | ||
| 151 | $inverseJoinColumn->quoted = true; | ||
| 152 | } | ||
| 153 | |||
| 154 | if ($inverseJoinColumn->referencedColumnName[0] === '`') { | ||
| 155 | $inverseJoinColumn->referencedColumnName = trim($inverseJoinColumn->referencedColumnName, '`'); | ||
| 156 | $inverseJoinColumn->quoted = true; | ||
| 157 | } | ||
| 158 | |||
| 159 | if (isset($inverseJoinColumn->onDelete) && strtolower($inverseJoinColumn->onDelete) === 'cascade') { | ||
| 160 | $mapping->isOnDeleteCascade = true; | ||
| 161 | } | ||
| 162 | |||
| 163 | $mapping->relationToTargetKeyColumns[$inverseJoinColumn->name] = $inverseJoinColumn->referencedColumnName; | ||
| 164 | $mapping->joinTableColumns[] = $inverseJoinColumn->name; | ||
| 165 | } | ||
| 166 | |||
| 167 | return $mapping; | ||
| 168 | } | ||
| 169 | |||
| 170 | /** @return list<string> */ | ||
| 171 | public function __sleep(): array | ||
| 172 | { | ||
| 173 | $serialized = parent::__sleep(); | ||
| 174 | $serialized[] = 'joinTable'; | ||
| 175 | $serialized[] = 'joinTableColumns'; | ||
| 176 | |||
| 177 | foreach (['relationToSourceKeyColumns', 'relationToTargetKeyColumns'] as $arrayKey) { | ||
| 178 | if ($this->$arrayKey !== null) { | ||
| 179 | $serialized[] = $arrayKey; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | return $serialized; | ||
| 184 | } | ||
| 185 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class ManyToOne implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * @param class-string|null $targetEntity | ||
| 14 | * @param string[]|null $cascade | ||
| 15 | * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch | ||
| 16 | */ | ||
| 17 | public function __construct( | ||
| 18 | public readonly string|null $targetEntity = null, | ||
| 19 | public readonly array|null $cascade = null, | ||
| 20 | public readonly string $fetch = 'LAZY', | ||
| 21 | public readonly string|null $inversedBy = null, | ||
| 22 | ) { | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ManyToOneAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/ManyToOneAssociationMapping.php new file mode 100644 index 0000000..3d1bf90 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ManyToOneAssociationMapping.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * The "many" side of a many-to-one association mapping is always the owning side. | ||
| 9 | */ | ||
| 10 | final class ManyToOneAssociationMapping extends ToOneOwningSideMapping | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/MappedSuperclass.php b/vendor/doctrine/orm/src/Mapping/MappedSuperclass.php new file mode 100644 index 0000000..29475a2 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/MappedSuperclass.php | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | use Doctrine\ORM\EntityRepository; | ||
| 9 | |||
| 10 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 11 | final class MappedSuperclass implements MappingAttribute | ||
| 12 | { | ||
| 13 | /** @psalm-param class-string<EntityRepository>|null $repositoryClass */ | ||
| 14 | public function __construct( | ||
| 15 | public readonly string|null $repositoryClass = null, | ||
| 16 | ) { | ||
| 17 | } | ||
| 18 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | /** A marker interface for mapping attributes. */ | ||
| 8 | interface MappingAttribute | ||
| 9 | { | ||
| 10 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/MappingException.php b/vendor/doctrine/orm/src/Mapping/MappingException.php new file mode 100644 index 0000000..9b73242 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/MappingException.php | |||
| @@ -0,0 +1,691 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use Doctrine\ORM\Exception\ORMException; | ||
| 9 | use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException; | ||
| 10 | use LibXMLError; | ||
| 11 | use ReflectionException; | ||
| 12 | use ValueError; | ||
| 13 | |||
| 14 | use function array_keys; | ||
| 15 | use function array_map; | ||
| 16 | use function array_values; | ||
| 17 | use function get_debug_type; | ||
| 18 | use function get_parent_class; | ||
| 19 | use function implode; | ||
| 20 | use function sprintf; | ||
| 21 | |||
| 22 | use const PHP_EOL; | ||
| 23 | |||
| 24 | /** | ||
| 25 | * A MappingException indicates that something is wrong with the mapping setup. | ||
| 26 | */ | ||
| 27 | class MappingException extends PersistenceMappingException implements ORMException | ||
| 28 | { | ||
| 29 | /** @param class-string $entityName */ | ||
| 30 | public static function identifierRequired(string $entityName): self | ||
| 31 | { | ||
| 32 | $parent = get_parent_class($entityName); | ||
| 33 | if ($parent !== false) { | ||
| 34 | return new self(sprintf( | ||
| 35 | 'No identifier/primary key specified for Entity "%s" sub class of "%s". Every Entity must have an identifier/primary key.', | ||
| 36 | $entityName, | ||
| 37 | $parent, | ||
| 38 | )); | ||
| 39 | } | ||
| 40 | |||
| 41 | return new self(sprintf( | ||
| 42 | 'No identifier/primary key specified for Entity "%s". Every Entity must have an identifier/primary key.', | ||
| 43 | $entityName, | ||
| 44 | )); | ||
| 45 | } | ||
| 46 | |||
| 47 | public static function invalidAssociationType(string $entityName, string $fieldName, int $type): self | ||
| 48 | { | ||
| 49 | return new self(sprintf( | ||
| 50 | 'The association "%s#%s" must be of type "ClassMetadata::ONE_TO_MANY", "ClassMetadata::MANY_TO_MANY" or "ClassMetadata::MANY_TO_ONE", "%d" given.', | ||
| 51 | $entityName, | ||
| 52 | $fieldName, | ||
| 53 | $type, | ||
| 54 | )); | ||
| 55 | } | ||
| 56 | |||
| 57 | public static function invalidInheritanceType(string $entityName, int $type): self | ||
| 58 | { | ||
| 59 | return new self(sprintf("The inheritance type '%s' specified for '%s' does not exist.", $type, $entityName)); | ||
| 60 | } | ||
| 61 | |||
| 62 | public static function generatorNotAllowedWithCompositeId(): self | ||
| 63 | { | ||
| 64 | return new self("Id generators can't be used with a composite id."); | ||
| 65 | } | ||
| 66 | |||
| 67 | public static function missingFieldName(string $entity): self | ||
| 68 | { | ||
| 69 | return new self(sprintf( | ||
| 70 | "The field or association mapping misses the 'fieldName' attribute in entity '%s'.", | ||
| 71 | $entity, | ||
| 72 | )); | ||
| 73 | } | ||
| 74 | |||
| 75 | public static function missingTargetEntity(string $fieldName): self | ||
| 76 | { | ||
| 77 | return new self(sprintf("The association mapping '%s' misses the 'targetEntity' attribute.", $fieldName)); | ||
| 78 | } | ||
| 79 | |||
| 80 | public static function missingSourceEntity(string $fieldName): self | ||
| 81 | { | ||
| 82 | return new self(sprintf("The association mapping '%s' misses the 'sourceEntity' attribute.", $fieldName)); | ||
| 83 | } | ||
| 84 | |||
| 85 | public static function missingEmbeddedClass(string $fieldName): self | ||
| 86 | { | ||
| 87 | return new self(sprintf("The embed mapping '%s' misses the 'class' attribute.", $fieldName)); | ||
| 88 | } | ||
| 89 | |||
| 90 | public static function mappingFileNotFound(string $entityName, string $fileName): self | ||
| 91 | { | ||
| 92 | return new self(sprintf("No mapping file found named '%s' for class '%s'.", $fileName, $entityName)); | ||
| 93 | } | ||
| 94 | |||
| 95 | /** | ||
| 96 | * Exception for invalid property name override. | ||
| 97 | * | ||
| 98 | * @param string $className The entity's name. | ||
| 99 | */ | ||
| 100 | public static function invalidOverrideFieldName(string $className, string $fieldName): self | ||
| 101 | { | ||
| 102 | return new self(sprintf("Invalid field override named '%s' for class '%s'.", $fieldName, $className)); | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Exception for invalid property type override. | ||
| 107 | * | ||
| 108 | * @param string $className The entity's name. | ||
| 109 | */ | ||
| 110 | public static function invalidOverrideFieldType(string $className, string $fieldName): self | ||
| 111 | { | ||
| 112 | return new self(sprintf( | ||
| 113 | "The column type of attribute '%s' on class '%s' could not be changed.", | ||
| 114 | $fieldName, | ||
| 115 | $className, | ||
| 116 | )); | ||
| 117 | } | ||
| 118 | |||
| 119 | public static function mappingNotFound(string $className, string $fieldName): self | ||
| 120 | { | ||
| 121 | return new self(sprintf("No mapping found for field '%s' on class '%s'.", $fieldName, $className)); | ||
| 122 | } | ||
| 123 | |||
| 124 | public static function queryNotFound(string $className, string $queryName): self | ||
| 125 | { | ||
| 126 | return new self(sprintf("No query found named '%s' on class '%s'.", $queryName, $className)); | ||
| 127 | } | ||
| 128 | |||
| 129 | public static function resultMappingNotFound(string $className, string $resultName): self | ||
| 130 | { | ||
| 131 | return new self(sprintf("No result set mapping found named '%s' on class '%s'.", $resultName, $className)); | ||
| 132 | } | ||
| 133 | |||
| 134 | public static function emptyQueryMapping(string $entity, string $queryName): self | ||
| 135 | { | ||
| 136 | return new self(sprintf('Query named "%s" in "%s" could not be empty.', $queryName, $entity)); | ||
| 137 | } | ||
| 138 | |||
| 139 | public static function nameIsMandatoryForQueryMapping(string $className): self | ||
| 140 | { | ||
| 141 | return new self(sprintf("Query name on entity class '%s' is not defined.", $className)); | ||
| 142 | } | ||
| 143 | |||
| 144 | public static function missingQueryMapping(string $entity, string $queryName): self | ||
| 145 | { | ||
| 146 | return new self(sprintf( | ||
| 147 | 'Query named "%s" in "%s requires a result class or result set mapping.', | ||
| 148 | $queryName, | ||
| 149 | $entity, | ||
| 150 | )); | ||
| 151 | } | ||
| 152 | |||
| 153 | public static function missingResultSetMappingEntity(string $entity, string $resultName): self | ||
| 154 | { | ||
| 155 | return new self(sprintf( | ||
| 156 | 'Result set mapping named "%s" in "%s requires a entity class name.', | ||
| 157 | $resultName, | ||
| 158 | $entity, | ||
| 159 | )); | ||
| 160 | } | ||
| 161 | |||
| 162 | public static function missingResultSetMappingFieldName(string $entity, string $resultName): self | ||
| 163 | { | ||
| 164 | return new self(sprintf( | ||
| 165 | 'Result set mapping named "%s" in "%s requires a field name.', | ||
| 166 | $resultName, | ||
| 167 | $entity, | ||
| 168 | )); | ||
| 169 | } | ||
| 170 | |||
| 171 | public static function oneToManyRequiresMappedBy(string $entityName, string $fieldName): MappingException | ||
| 172 | { | ||
| 173 | return new self(sprintf( | ||
| 174 | "OneToMany mapping on entity '%s' field '%s' requires the 'mappedBy' attribute.", | ||
| 175 | $entityName, | ||
| 176 | $fieldName, | ||
| 177 | )); | ||
| 178 | } | ||
| 179 | |||
| 180 | public static function joinTableRequired(string $fieldName): self | ||
| 181 | { | ||
| 182 | return new self(sprintf("The mapping of field '%s' requires an the 'joinTable' attribute.", $fieldName)); | ||
| 183 | } | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Called if a required option was not found but is required | ||
| 187 | * | ||
| 188 | * @param string $field Which field cannot be processed? | ||
| 189 | * @param string $expectedOption Which option is required | ||
| 190 | * @param string $hint Can optionally be used to supply a tip for common mistakes, | ||
| 191 | * e.g. "Did you think of the plural s?" | ||
| 192 | */ | ||
| 193 | public static function missingRequiredOption(string $field, string $expectedOption, string $hint = ''): self | ||
| 194 | { | ||
| 195 | $message = "The mapping of field '" . $field . "' is invalid: The option '" . $expectedOption . "' is required."; | ||
| 196 | |||
| 197 | if (! empty($hint)) { | ||
| 198 | $message .= ' (Hint: ' . $hint . ')'; | ||
| 199 | } | ||
| 200 | |||
| 201 | return new self($message); | ||
| 202 | } | ||
| 203 | |||
| 204 | /** | ||
| 205 | * Generic exception for invalid mappings. | ||
| 206 | */ | ||
| 207 | public static function invalidMapping(string $fieldName): self | ||
| 208 | { | ||
| 209 | return new self(sprintf("The mapping of field '%s' is invalid.", $fieldName)); | ||
| 210 | } | ||
| 211 | |||
| 212 | /** | ||
| 213 | * Exception for reflection exceptions - adds the entity name, | ||
| 214 | * because there might be long classnames that will be shortened | ||
| 215 | * within the stacktrace | ||
| 216 | * | ||
| 217 | * @param string $entity The entity's name | ||
| 218 | */ | ||
| 219 | public static function reflectionFailure(string $entity, ReflectionException $previousException): self | ||
| 220 | { | ||
| 221 | return new self(sprintf('An error occurred in %s', $entity), 0, $previousException); | ||
| 222 | } | ||
| 223 | |||
| 224 | public static function joinColumnMustPointToMappedField(string $className, string $joinColumn): self | ||
| 225 | { | ||
| 226 | return new self(sprintf( | ||
| 227 | 'The column %s must be mapped to a field in class %s since it is referenced by a join column of another class.', | ||
| 228 | $joinColumn, | ||
| 229 | $className, | ||
| 230 | )); | ||
| 231 | } | ||
| 232 | |||
| 233 | public static function joinColumnNotAllowedOnOneToOneInverseSide(string $className, string $fieldName): self | ||
| 234 | { | ||
| 235 | return new self(sprintf( | ||
| 236 | '%s#%s is a OneToOne inverse side, which does not allow join columns.', | ||
| 237 | $className, | ||
| 238 | $fieldName, | ||
| 239 | )); | ||
| 240 | } | ||
| 241 | |||
| 242 | /** @param class-string $className */ | ||
| 243 | public static function classIsNotAValidEntityOrMappedSuperClass(string $className): self | ||
| 244 | { | ||
| 245 | $parent = get_parent_class($className); | ||
| 246 | if ($parent !== false) { | ||
| 247 | return new self(sprintf( | ||
| 248 | 'Class "%s" sub class of "%s" is not a valid entity or mapped super class.', | ||
| 249 | $className, | ||
| 250 | $parent, | ||
| 251 | )); | ||
| 252 | } | ||
| 253 | |||
| 254 | return new self(sprintf( | ||
| 255 | 'Class "%s" is not a valid entity or mapped super class.', | ||
| 256 | $className, | ||
| 257 | )); | ||
| 258 | } | ||
| 259 | |||
| 260 | /** | ||
| 261 | * @param string $entity The entity's name. | ||
| 262 | * @param string $fieldName The name of the field that was already declared. | ||
| 263 | */ | ||
| 264 | public static function duplicateFieldMapping(string $entity, string $fieldName): self | ||
| 265 | { | ||
| 266 | return new self(sprintf( | ||
| 267 | 'Property "%s" in "%s" was already declared, but it must be declared only once', | ||
| 268 | $fieldName, | ||
| 269 | $entity, | ||
| 270 | )); | ||
| 271 | } | ||
| 272 | |||
| 273 | public static function duplicateAssociationMapping(string $entity, string $fieldName): self | ||
| 274 | { | ||
| 275 | return new self(sprintf( | ||
| 276 | 'Property "%s" in "%s" was already declared, but it must be declared only once', | ||
| 277 | $fieldName, | ||
| 278 | $entity, | ||
| 279 | )); | ||
| 280 | } | ||
| 281 | |||
| 282 | public static function duplicateQueryMapping(string $entity, string $queryName): self | ||
| 283 | { | ||
| 284 | return new self(sprintf( | ||
| 285 | 'Query named "%s" in "%s" was already declared, but it must be declared only once', | ||
| 286 | $queryName, | ||
| 287 | $entity, | ||
| 288 | )); | ||
| 289 | } | ||
| 290 | |||
| 291 | public static function duplicateResultSetMapping(string $entity, string $resultName): self | ||
| 292 | { | ||
| 293 | return new self(sprintf( | ||
| 294 | 'Result set mapping named "%s" in "%s" was already declared, but it must be declared only once', | ||
| 295 | $resultName, | ||
| 296 | $entity, | ||
| 297 | )); | ||
| 298 | } | ||
| 299 | |||
| 300 | public static function singleIdNotAllowedOnCompositePrimaryKey(string $entity): self | ||
| 301 | { | ||
| 302 | return new self('Single id is not allowed on composite primary key in entity ' . $entity); | ||
| 303 | } | ||
| 304 | |||
| 305 | public static function noIdDefined(string $entity): self | ||
| 306 | { | ||
| 307 | return new self('No ID defined for entity ' . $entity); | ||
| 308 | } | ||
| 309 | |||
| 310 | public static function unsupportedOptimisticLockingType(string $entity, string $fieldName, string $unsupportedType): self | ||
| 311 | { | ||
| 312 | return new self(sprintf( | ||
| 313 | 'Locking type "%s" (specified in "%s", field "%s") is not supported by Doctrine.', | ||
| 314 | $unsupportedType, | ||
| 315 | $entity, | ||
| 316 | $fieldName, | ||
| 317 | )); | ||
| 318 | } | ||
| 319 | |||
| 320 | public static function fileMappingDriversRequireConfiguredDirectoryPath(string|null $path = null): self | ||
| 321 | { | ||
| 322 | if (! empty($path)) { | ||
| 323 | $path = '[' . $path . ']'; | ||
| 324 | } | ||
| 325 | |||
| 326 | return new self( | ||
| 327 | 'File mapping drivers must have a valid directory path, ' . | ||
| 328 | 'however the given path ' . $path . ' seems to be incorrect!', | ||
| 329 | ); | ||
| 330 | } | ||
| 331 | |||
| 332 | /** | ||
| 333 | * Returns an exception that indicates that a class used in a discriminator map does not exist. | ||
| 334 | * An example would be an outdated (maybe renamed) classname. | ||
| 335 | * | ||
| 336 | * @param string $className The class that could not be found | ||
| 337 | * @param string $owningClass The class that declares the discriminator map. | ||
| 338 | */ | ||
| 339 | public static function invalidClassInDiscriminatorMap(string $className, string $owningClass): self | ||
| 340 | { | ||
| 341 | return new self(sprintf( | ||
| 342 | "Entity class '%s' used in the discriminator map of class '%s' " . | ||
| 343 | 'does not exist.', | ||
| 344 | $className, | ||
| 345 | $owningClass, | ||
| 346 | )); | ||
| 347 | } | ||
| 348 | |||
| 349 | /** | ||
| 350 | * @param string[] $entries | ||
| 351 | * @param array<string,string> $map | ||
| 352 | */ | ||
| 353 | public static function duplicateDiscriminatorEntry(string $className, array $entries, array $map): self | ||
| 354 | { | ||
| 355 | return new self( | ||
| 356 | 'The entries ' . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . | ||
| 357 | 'If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. ' . | ||
| 358 | 'The entries of the current map are: @DiscriminatorMap({' . implode(', ', array_map( | ||
| 359 | static fn ($a, $b) => sprintf("'%s': '%s'", $a, $b), | ||
| 360 | array_keys($map), | ||
| 361 | array_values($map), | ||
| 362 | )) . '})', | ||
| 363 | ); | ||
| 364 | } | ||
| 365 | |||
| 366 | /** | ||
| 367 | * @param class-string $rootEntityClass | ||
| 368 | * @param class-string $childEntityClass | ||
| 369 | */ | ||
| 370 | public static function missingInheritanceTypeDeclaration(string $rootEntityClass, string $childEntityClass): self | ||
| 371 | { | ||
| 372 | return new self(sprintf( | ||
| 373 | "Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared.", | ||
| 374 | $childEntityClass, | ||
| 375 | $rootEntityClass, | ||
| 376 | )); | ||
| 377 | } | ||
| 378 | |||
| 379 | public static function missingDiscriminatorMap(string $className): self | ||
| 380 | { | ||
| 381 | return new self(sprintf( | ||
| 382 | "Entity class '%s' is using inheritance but no discriminator map was defined.", | ||
| 383 | $className, | ||
| 384 | )); | ||
| 385 | } | ||
| 386 | |||
| 387 | public static function missingDiscriminatorColumn(string $className): self | ||
| 388 | { | ||
| 389 | return new self(sprintf( | ||
| 390 | "Entity class '%s' is using inheritance but no discriminator column was defined.", | ||
| 391 | $className, | ||
| 392 | )); | ||
| 393 | } | ||
| 394 | |||
| 395 | public static function invalidDiscriminatorColumnType(string $className, string $type): self | ||
| 396 | { | ||
| 397 | return new self(sprintf( | ||
| 398 | "Discriminator column type on entity class '%s' is not allowed to be '%s'. 'string' or 'integer' type variables are suggested!", | ||
| 399 | $className, | ||
| 400 | $type, | ||
| 401 | )); | ||
| 402 | } | ||
| 403 | |||
| 404 | public static function nameIsMandatoryForDiscriminatorColumns(string $className): self | ||
| 405 | { | ||
| 406 | return new self(sprintf("Discriminator column name on entity class '%s' is not defined.", $className)); | ||
| 407 | } | ||
| 408 | |||
| 409 | public static function cannotVersionIdField(string $className, string $fieldName): self | ||
| 410 | { | ||
| 411 | return new self(sprintf( | ||
| 412 | "Setting Id field '%s' as versionable in entity class '%s' is not supported.", | ||
| 413 | $fieldName, | ||
| 414 | $className, | ||
| 415 | )); | ||
| 416 | } | ||
| 417 | |||
| 418 | public static function duplicateColumnName(string $className, string $columnName): self | ||
| 419 | { | ||
| 420 | return new self("Duplicate definition of column '" . $columnName . "' on entity '" . $className . "' in a field or discriminator column mapping."); | ||
| 421 | } | ||
| 422 | |||
| 423 | public static function illegalToManyAssociationOnMappedSuperclass(string $className, string $field): self | ||
| 424 | { | ||
| 425 | return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '" . $className . '#' . $field . "'."); | ||
| 426 | } | ||
| 427 | |||
| 428 | public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId(string $className, string $targetEntity, string $targetField): self | ||
| 429 | { | ||
| 430 | return new self("It is not possible to map entity '" . $className . "' with a composite primary key " . | ||
| 431 | "as part of the primary key of another entity '" . $targetEntity . '#' . $targetField . "'."); | ||
| 432 | } | ||
| 433 | |||
| 434 | public static function noSingleAssociationJoinColumnFound(string $className, string $field): self | ||
| 435 | { | ||
| 436 | return new self(sprintf("'%s#%s' is not an association with a single join column.", $className, $field)); | ||
| 437 | } | ||
| 438 | |||
| 439 | public static function noFieldNameFoundForColumn(string $className, string $column): self | ||
| 440 | { | ||
| 441 | return new self(sprintf( | ||
| 442 | "Cannot find a field on '%s' that is mapped to column '%s'. Either the " . | ||
| 443 | 'field does not exist or an association exists but it has multiple join columns.', | ||
| 444 | $className, | ||
| 445 | $column, | ||
| 446 | )); | ||
| 447 | } | ||
| 448 | |||
| 449 | public static function illegalOrphanRemovalOnIdentifierAssociation(string $className, string $field): self | ||
| 450 | { | ||
| 451 | return new self(sprintf( | ||
| 452 | "The orphan removal option is not allowed on an association that is part of the identifier in '%s#%s'.", | ||
| 453 | $className, | ||
| 454 | $field, | ||
| 455 | )); | ||
| 456 | } | ||
| 457 | |||
| 458 | public static function illegalOrphanRemoval(string $className, string $field): self | ||
| 459 | { | ||
| 460 | return new self('Orphan removal is only allowed on one-to-one and one-to-many ' . | ||
| 461 | 'associations, but ' . $className . '#' . $field . ' is not.'); | ||
| 462 | } | ||
| 463 | |||
| 464 | public static function illegalInverseIdentifierAssociation(string $className, string $field): self | ||
| 465 | { | ||
| 466 | return new self(sprintf( | ||
| 467 | "An inverse association is not allowed to be identifier in '%s#%s'.", | ||
| 468 | $className, | ||
| 469 | $field, | ||
| 470 | )); | ||
| 471 | } | ||
| 472 | |||
| 473 | public static function illegalToManyIdentifierAssociation(string $className, string $field): self | ||
| 474 | { | ||
| 475 | return new self(sprintf( | ||
| 476 | "Many-to-many or one-to-many associations are not allowed to be identifier in '%s#%s'.", | ||
| 477 | $className, | ||
| 478 | $field, | ||
| 479 | )); | ||
| 480 | } | ||
| 481 | |||
| 482 | public static function noInheritanceOnMappedSuperClass(string $className): self | ||
| 483 | { | ||
| 484 | return new self("It is not supported to define inheritance information on a mapped superclass '" . $className . "'."); | ||
| 485 | } | ||
| 486 | |||
| 487 | public static function mappedClassNotPartOfDiscriminatorMap(string $className, string $rootClassName): self | ||
| 488 | { | ||
| 489 | return new self( | ||
| 490 | "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . | ||
| 491 | "to be properly mapped in the inheritance hierarchy. Alternatively you can make '" . $className . "' an abstract class " . | ||
| 492 | 'to avoid this exception from occurring.', | ||
| 493 | ); | ||
| 494 | } | ||
| 495 | |||
| 496 | public static function lifecycleCallbackMethodNotFound(string $className, string $methodName): self | ||
| 497 | { | ||
| 498 | return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); | ||
| 499 | } | ||
| 500 | |||
| 501 | /** @param class-string $className */ | ||
| 502 | public static function illegalLifecycleCallbackOnEmbeddedClass(string $event, string $className): self | ||
| 503 | { | ||
| 504 | return new self(sprintf( | ||
| 505 | <<<'EXCEPTION' | ||
| 506 | Context: Attempt to register lifecycle callback "%s" on embedded class "%s". | ||
| 507 | Problem: Registering lifecycle callbacks on embedded classes is not allowed. | ||
| 508 | EXCEPTION, | ||
| 509 | $event, | ||
| 510 | $className, | ||
| 511 | )); | ||
| 512 | } | ||
| 513 | |||
| 514 | public static function entityListenerClassNotFound(string $listenerName, string $className): self | ||
| 515 | { | ||
| 516 | return new self(sprintf('Entity Listener "%s" declared on "%s" not found.', $listenerName, $className)); | ||
| 517 | } | ||
| 518 | |||
| 519 | public static function entityListenerMethodNotFound(string $listenerName, string $methodName, string $className): self | ||
| 520 | { | ||
| 521 | return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName)); | ||
| 522 | } | ||
| 523 | |||
| 524 | public static function duplicateEntityListener(string $listenerName, string $methodName, string $className): self | ||
| 525 | { | ||
| 526 | return new self(sprintf('Entity Listener "%s#%s()" in "%s" was already declared, but it must be declared only once.', $listenerName, $methodName, $className)); | ||
| 527 | } | ||
| 528 | |||
| 529 | /** @param class-string $className */ | ||
| 530 | public static function invalidFetchMode(string $className, string $fetchMode): self | ||
| 531 | { | ||
| 532 | return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $fetchMode . "'"); | ||
| 533 | } | ||
| 534 | |||
| 535 | public static function invalidGeneratedMode(int|string $generatedMode): self | ||
| 536 | { | ||
| 537 | return new self("Invalid generated mode '" . $generatedMode . "'"); | ||
| 538 | } | ||
| 539 | |||
| 540 | public static function compositeKeyAssignedIdGeneratorRequired(string $className): self | ||
| 541 | { | ||
| 542 | return new self("Entity '" . $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported."); | ||
| 543 | } | ||
| 544 | |||
| 545 | public static function invalidTargetEntityClass(string $targetEntity, string $sourceEntity, string $associationName): self | ||
| 546 | { | ||
| 547 | return new self('The target-entity ' . $targetEntity . " cannot be found in '" . $sourceEntity . '#' . $associationName . "'."); | ||
| 548 | } | ||
| 549 | |||
| 550 | /** @param string[] $cascades */ | ||
| 551 | public static function invalidCascadeOption(array $cascades, string $className, string $propertyName): self | ||
| 552 | { | ||
| 553 | $cascades = implode(', ', array_map(static fn (string $e): string => "'" . $e . "'", $cascades)); | ||
| 554 | |||
| 555 | return new self(sprintf( | ||
| 556 | "You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', and 'detach'", | ||
| 557 | $className, | ||
| 558 | $propertyName, | ||
| 559 | $cascades, | ||
| 560 | )); | ||
| 561 | } | ||
| 562 | |||
| 563 | public static function missingSequenceName(string $className): self | ||
| 564 | { | ||
| 565 | return new self( | ||
| 566 | sprintf('Missing "sequenceName" attribute for sequence id generator definition on class "%s".', $className), | ||
| 567 | ); | ||
| 568 | } | ||
| 569 | |||
| 570 | public static function infiniteEmbeddableNesting(string $className, string $propertyName): self | ||
| 571 | { | ||
| 572 | return new self( | ||
| 573 | sprintf( | ||
| 574 | 'Infinite nesting detected for embedded property %s::%s. ' . | ||
| 575 | 'You cannot embed an embeddable from the same type inside an embeddable.', | ||
| 576 | $className, | ||
| 577 | $propertyName, | ||
| 578 | ), | ||
| 579 | ); | ||
| 580 | } | ||
| 581 | |||
| 582 | public static function illegalOverrideOfInheritedProperty(string $className, string $propertyName, string $inheritFromClass): self | ||
| 583 | { | ||
| 584 | return new self( | ||
| 585 | sprintf( | ||
| 586 | '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.', | ||
| 587 | $className, | ||
| 588 | $propertyName, | ||
| 589 | $inheritFromClass, | ||
| 590 | ), | ||
| 591 | ); | ||
| 592 | } | ||
| 593 | |||
| 594 | public static function invalidIndexConfiguration(string $className, string $indexName): self | ||
| 595 | { | ||
| 596 | return new self( | ||
| 597 | sprintf( | ||
| 598 | 'Index %s for entity %s should contain columns or fields values, but not both.', | ||
| 599 | $indexName, | ||
| 600 | $className, | ||
| 601 | ), | ||
| 602 | ); | ||
| 603 | } | ||
| 604 | |||
| 605 | public static function invalidUniqueConstraintConfiguration(string $className, string $indexName): self | ||
| 606 | { | ||
| 607 | return new self( | ||
| 608 | sprintf( | ||
| 609 | 'Unique constraint %s for entity %s should contain columns or fields values, but not both.', | ||
| 610 | $indexName, | ||
| 611 | $className, | ||
| 612 | ), | ||
| 613 | ); | ||
| 614 | } | ||
| 615 | |||
| 616 | public static function invalidOverrideType(string $expectdType, mixed $givenValue): self | ||
| 617 | { | ||
| 618 | return new self(sprintf( | ||
| 619 | 'Expected %s, but %s was given.', | ||
| 620 | $expectdType, | ||
| 621 | get_debug_type($givenValue), | ||
| 622 | )); | ||
| 623 | } | ||
| 624 | |||
| 625 | public static function backedEnumTypeRequired(string $className, string $fieldName, string $enumType): self | ||
| 626 | { | ||
| 627 | return new self(sprintf( | ||
| 628 | 'Attempting to map a non-backed enum type %s in entity %s::$%s. Please use backed enums only', | ||
| 629 | $enumType, | ||
| 630 | $className, | ||
| 631 | $fieldName, | ||
| 632 | )); | ||
| 633 | } | ||
| 634 | |||
| 635 | public static function nonEnumTypeMapped(string $className, string $fieldName, string $enumType): self | ||
| 636 | { | ||
| 637 | return new self(sprintf( | ||
| 638 | 'Attempting to map non-enum type %s as enum in entity %s::$%s', | ||
| 639 | $enumType, | ||
| 640 | $className, | ||
| 641 | $fieldName, | ||
| 642 | )); | ||
| 643 | } | ||
| 644 | |||
| 645 | /** | ||
| 646 | * @param class-string $className | ||
| 647 | * @param class-string<BackedEnum> $enumType | ||
| 648 | */ | ||
| 649 | public static function invalidEnumValue( | ||
| 650 | string $className, | ||
| 651 | string $fieldName, | ||
| 652 | string $value, | ||
| 653 | string $enumType, | ||
| 654 | ValueError $previous, | ||
| 655 | ): self { | ||
| 656 | return new self(sprintf( | ||
| 657 | <<<'EXCEPTION' | ||
| 658 | Context: Trying to hydrate enum property "%s::$%s" | ||
| 659 | Problem: Case "%s" is not listed in enum "%s" | ||
| 660 | Solution: Either add the case to the enum type or migrate the database column to use another case of the enum | ||
| 661 | EXCEPTION | ||
| 662 | , | ||
| 663 | $className, | ||
| 664 | $fieldName, | ||
| 665 | $value, | ||
| 666 | $enumType, | ||
| 667 | ), 0, $previous); | ||
| 668 | } | ||
| 669 | |||
| 670 | /** @param LibXMLError[] $errors */ | ||
| 671 | public static function fromLibXmlErrors(array $errors): self | ||
| 672 | { | ||
| 673 | $formatter = static fn (LibXMLError $error): string => sprintf( | ||
| 674 | 'libxml error: %s in %s at line %d', | ||
| 675 | $error->message, | ||
| 676 | $error->file, | ||
| 677 | $error->line, | ||
| 678 | ); | ||
| 679 | |||
| 680 | return new self(implode(PHP_EOL, array_map($formatter, $errors))); | ||
| 681 | } | ||
| 682 | |||
| 683 | public static function invalidAttributeOnEmbeddable(string $entityName, string $attributeName): self | ||
| 684 | { | ||
| 685 | return new self(sprintf( | ||
| 686 | 'Attribute "%s" on embeddable "%s" is not allowed.', | ||
| 687 | $attributeName, | ||
| 688 | $entityName, | ||
| 689 | )); | ||
| 690 | } | ||
| 691 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * A set of rules for determining the physical column and table names | ||
| 9 | * | ||
| 10 | * @link www.doctrine-project.org | ||
| 11 | */ | ||
| 12 | interface NamingStrategy | ||
| 13 | { | ||
| 14 | /** | ||
| 15 | * Returns a table name for an entity class. | ||
| 16 | * | ||
| 17 | * @param class-string $className | ||
| 18 | */ | ||
| 19 | public function classToTableName(string $className): string; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Returns a column name for a property. | ||
| 23 | * | ||
| 24 | * @param class-string $className | ||
| 25 | */ | ||
| 26 | public function propertyToColumnName(string $propertyName, string $className): string; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Returns a column name for an embedded property. | ||
| 30 | * | ||
| 31 | * @param class-string $className | ||
| 32 | * @param class-string $embeddedClassName | ||
| 33 | */ | ||
| 34 | public function embeddedFieldToColumnName( | ||
| 35 | string $propertyName, | ||
| 36 | string $embeddedColumnName, | ||
| 37 | string $className, | ||
| 38 | string $embeddedClassName, | ||
| 39 | ): string; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Returns the default reference column name. | ||
| 43 | */ | ||
| 44 | public function referenceColumnName(): string; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Returns a join column name for a property. | ||
| 48 | * | ||
| 49 | * @param class-string $className | ||
| 50 | */ | ||
| 51 | public function joinColumnName(string $propertyName, string $className): string; | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Returns a join table name. | ||
| 55 | * | ||
| 56 | * @param class-string $sourceEntity | ||
| 57 | * @param class-string $targetEntity | ||
| 58 | */ | ||
| 59 | public function joinTableName(string $sourceEntity, string $targetEntity, string $propertyName): string; | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Returns the foreign key column name for the given parameters. | ||
| 63 | * | ||
| 64 | * @param class-string $entityName An entity. | ||
| 65 | * @param string|null $referencedColumnName A property name or null in | ||
| 66 | * case of a self-referencing | ||
| 67 | * entity with join columns | ||
| 68 | * defined in the mapping | ||
| 69 | */ | ||
| 70 | public function joinKeyColumnName(string $entityName, string|null $referencedColumnName): string; | ||
| 71 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/OneToMany.php b/vendor/doctrine/orm/src/Mapping/OneToMany.php new file mode 100644 index 0000000..d71c4f9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToMany.php | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class OneToMany implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * @param class-string|null $targetEntity | ||
| 14 | * @param string[]|null $cascade | ||
| 15 | * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch | ||
| 16 | */ | ||
| 17 | public function __construct( | ||
| 18 | public readonly string|null $targetEntity = null, | ||
| 19 | public readonly string|null $mappedBy = null, | ||
| 20 | public readonly array|null $cascade = null, | ||
| 21 | public readonly string $fetch = 'LAZY', | ||
| 22 | public readonly bool $orphanRemoval = false, | ||
| 23 | public readonly string|null $indexBy = null, | ||
| 24 | ) { | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/OneToManyAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/OneToManyAssociationMapping.php new file mode 100644 index 0000000..786e981 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToManyAssociationMapping.php | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | final class OneToManyAssociationMapping extends ToManyInverseSideMapping | ||
| 8 | { | ||
| 9 | /** | ||
| 10 | * @param mixed[] $mappingArray | ||
| 11 | * @psalm-param array{ | ||
| 12 | * fieldName: string, | ||
| 13 | * sourceEntity: class-string, | ||
| 14 | * targetEntity: class-string, | ||
| 15 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 16 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 17 | * inherited?: class-string|null, | ||
| 18 | * declared?: class-string|null, | ||
| 19 | * cache?: array<mixed>|null, | ||
| 20 | * id?: bool|null, | ||
| 21 | * isOnDeleteCascade?: bool|null, | ||
| 22 | * originalClass?: class-string|null, | ||
| 23 | * originalField?: string|null, | ||
| 24 | * orphanRemoval?: bool, | ||
| 25 | * unique?: bool|null, | ||
| 26 | * joinTable?: mixed[]|null, | ||
| 27 | * type?: int, | ||
| 28 | * isOwningSide: bool, | ||
| 29 | * } $mappingArray | ||
| 30 | */ | ||
| 31 | public static function fromMappingArray(array $mappingArray): static | ||
| 32 | { | ||
| 33 | $mapping = parent::fromMappingArray($mappingArray); | ||
| 34 | |||
| 35 | if ($mapping->orphanRemoval && ! $mapping->isCascadeRemove()) { | ||
| 36 | $mapping->cascade[] = 'remove'; | ||
| 37 | } | ||
| 38 | |||
| 39 | return $mapping; | ||
| 40 | } | ||
| 41 | |||
| 42 | /** | ||
| 43 | * @param mixed[] $mappingArray | ||
| 44 | * @psalm-param array{ | ||
| 45 | * fieldName: string, | ||
| 46 | * sourceEntity: class-string, | ||
| 47 | * targetEntity: class-string, | ||
| 48 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 49 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 50 | * inherited?: class-string|null, | ||
| 51 | * declared?: class-string|null, | ||
| 52 | * cache?: array<mixed>|null, | ||
| 53 | * id?: bool|null, | ||
| 54 | * isOnDeleteCascade?: bool|null, | ||
| 55 | * originalClass?: class-string|null, | ||
| 56 | * originalField?: string|null, | ||
| 57 | * orphanRemoval?: bool, | ||
| 58 | * unique?: bool|null, | ||
| 59 | * joinTable?: mixed[]|null, | ||
| 60 | * type?: int, | ||
| 61 | * isOwningSide: bool, | ||
| 62 | * } $mappingArray | ||
| 63 | */ | ||
| 64 | public static function fromMappingArrayAndName(array $mappingArray, string $name): static | ||
| 65 | { | ||
| 66 | $mapping = self::fromMappingArray($mappingArray); | ||
| 67 | |||
| 68 | // OneToMany-side MUST be inverse (must have mappedBy) | ||
| 69 | if (! isset($mapping->mappedBy)) { | ||
| 70 | throw MappingException::oneToManyRequiresMappedBy($name, $mapping->fieldName); | ||
| 71 | } | ||
| 72 | |||
| 73 | return $mapping; | ||
| 74 | } | ||
| 75 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class OneToOne implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * @param class-string|null $targetEntity | ||
| 14 | * @param array<string>|null $cascade | ||
| 15 | * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch | ||
| 16 | */ | ||
| 17 | public function __construct( | ||
| 18 | public readonly string|null $targetEntity = null, | ||
| 19 | public readonly string|null $mappedBy = null, | ||
| 20 | public readonly string|null $inversedBy = null, | ||
| 21 | public readonly array|null $cascade = null, | ||
| 22 | public readonly string $fetch = 'LAZY', | ||
| 23 | public readonly bool $orphanRemoval = false, | ||
| 24 | ) { | ||
| 25 | } | ||
| 26 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | interface OneToOneAssociationMapping extends ToOneAssociationMapping | ||
| 8 | { | ||
| 9 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/OneToOneInverseSideMapping.php b/vendor/doctrine/orm/src/Mapping/OneToOneInverseSideMapping.php new file mode 100644 index 0000000..85e0f30 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToOneInverseSideMapping.php | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | final class OneToOneInverseSideMapping extends ToOneInverseSideMapping implements OneToOneAssociationMapping | ||
| 8 | { | ||
| 9 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/OneToOneOwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/OneToOneOwningSideMapping.php new file mode 100644 index 0000000..4ad181b --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OneToOneOwningSideMapping.php | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | final class OneToOneOwningSideMapping extends ToOneOwningSideMapping implements OneToOneAssociationMapping | ||
| 8 | { | ||
| 9 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/OrderBy.php b/vendor/doctrine/orm/src/Mapping/OrderBy.php new file mode 100644 index 0000000..5cb2ed9 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/OrderBy.php | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class OrderBy implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** @param array<string> $value */ | ||
| 13 | public function __construct( | ||
| 14 | public readonly array $value, | ||
| 15 | ) { | ||
| 16 | } | ||
| 17 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | abstract class OwningSideMapping extends AssociationMapping | ||
| 8 | { | ||
| 9 | /** | ||
| 10 | * required for bidirectional associations | ||
| 11 | * The name of the field that completes the bidirectional association on | ||
| 12 | * the inverse side. This key must be specified on the owning side of a | ||
| 13 | * bidirectional association. | ||
| 14 | */ | ||
| 15 | public string|null $inversedBy = null; | ||
| 16 | |||
| 17 | /** @return list<string> */ | ||
| 18 | public function __sleep(): array | ||
| 19 | { | ||
| 20 | $serialized = parent::__sleep(); | ||
| 21 | |||
| 22 | if ($this->inversedBy !== null) { | ||
| 23 | $serialized[] = 'inversedBy'; | ||
| 24 | } | ||
| 25 | |||
| 26 | return $serialized; | ||
| 27 | } | ||
| 28 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PostLoad implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PostPersist.php b/vendor/doctrine/orm/src/Mapping/PostPersist.php new file mode 100644 index 0000000..f7bf2e1 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PostPersist.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PostPersist implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PostRemove.php b/vendor/doctrine/orm/src/Mapping/PostRemove.php new file mode 100644 index 0000000..394c175 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PostRemove.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PostRemove implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PostUpdate.php b/vendor/doctrine/orm/src/Mapping/PostUpdate.php new file mode 100644 index 0000000..7b95675 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PostUpdate.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PostUpdate implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PreFlush.php b/vendor/doctrine/orm/src/Mapping/PreFlush.php new file mode 100644 index 0000000..f2c09d7 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PreFlush.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PreFlush implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PrePersist.php b/vendor/doctrine/orm/src/Mapping/PrePersist.php new file mode 100644 index 0000000..1b88a7a --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PrePersist.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PrePersist implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PreRemove.php b/vendor/doctrine/orm/src/Mapping/PreRemove.php new file mode 100644 index 0000000..f63d4e0 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PreRemove.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PreRemove implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/PreUpdate.php b/vendor/doctrine/orm/src/Mapping/PreUpdate.php new file mode 100644 index 0000000..9b73bfd --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/PreUpdate.php | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_METHOD)] | ||
| 10 | final class PreUpdate implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/QuoteStrategy.php b/vendor/doctrine/orm/src/Mapping/QuoteStrategy.php new file mode 100644 index 0000000..9eb3e53 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/QuoteStrategy.php | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * A set of rules for determining the column, alias and table quotes. | ||
| 11 | */ | ||
| 12 | interface QuoteStrategy | ||
| 13 | { | ||
| 14 | /** | ||
| 15 | * Gets the (possibly quoted) column name for safe use in an SQL statement. | ||
| 16 | */ | ||
| 17 | public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Gets the (possibly quoted) primary table name for safe use in an SQL statement. | ||
| 21 | */ | ||
| 22 | public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string; | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Gets the (possibly quoted) sequence name for safe use in an SQL statement. | ||
| 26 | * | ||
| 27 | * @param mixed[] $definition | ||
| 28 | */ | ||
| 29 | public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string; | ||
| 30 | |||
| 31 | /** Gets the (possibly quoted) name of the join table. */ | ||
| 32 | public function getJoinTableName( | ||
| 33 | ManyToManyOwningSideMapping $association, | ||
| 34 | ClassMetadata $class, | ||
| 35 | AbstractPlatform $platform, | ||
| 36 | ): string; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Gets the (possibly quoted) join column name. | ||
| 40 | */ | ||
| 41 | public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Gets the (possibly quoted) join column name. | ||
| 45 | */ | ||
| 46 | public function getReferencedJoinColumnName( | ||
| 47 | JoinColumnMapping $joinColumn, | ||
| 48 | ClassMetadata $class, | ||
| 49 | AbstractPlatform $platform, | ||
| 50 | ): string; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. | ||
| 54 | * | ||
| 55 | * @psalm-return list<string> | ||
| 56 | */ | ||
| 57 | public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array; | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Gets the column alias. | ||
| 61 | */ | ||
| 62 | public function getColumnAlias( | ||
| 63 | string $columnName, | ||
| 64 | int $counter, | ||
| 65 | AbstractPlatform $platform, | ||
| 66 | ClassMetadata|null $class = null, | ||
| 67 | ): string; | ||
| 68 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Doctrine\Instantiator\Instantiator; | ||
| 8 | use ReflectionProperty; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Acts as a proxy to a nested Property structure, making it look like | ||
| 12 | * just a single scalar property. | ||
| 13 | * | ||
| 14 | * This way value objects "just work" without UnitOfWork, Persisters or Hydrators | ||
| 15 | * needing any changes. | ||
| 16 | * | ||
| 17 | * TODO: Move this class into Common\Reflection | ||
| 18 | */ | ||
| 19 | final class ReflectionEmbeddedProperty extends ReflectionProperty | ||
| 20 | { | ||
| 21 | private Instantiator|null $instantiator = null; | ||
| 22 | |||
| 23 | /** | ||
| 24 | * @param ReflectionProperty $parentProperty reflection property of the class where the embedded object has to be put | ||
| 25 | * @param ReflectionProperty $childProperty reflection property of the embedded object | ||
| 26 | * @psalm-param class-string $embeddedClass | ||
| 27 | */ | ||
| 28 | public function __construct( | ||
| 29 | private readonly ReflectionProperty $parentProperty, | ||
| 30 | private readonly ReflectionProperty $childProperty, | ||
| 31 | private readonly string $embeddedClass, | ||
| 32 | ) { | ||
| 33 | parent::__construct($childProperty->getDeclaringClass()->name, $childProperty->getName()); | ||
| 34 | } | ||
| 35 | |||
| 36 | public function getValue(object|null $object = null): mixed | ||
| 37 | { | ||
| 38 | $embeddedObject = $this->parentProperty->getValue($object); | ||
| 39 | |||
| 40 | if ($embeddedObject === null) { | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | |||
| 44 | return $this->childProperty->getValue($embeddedObject); | ||
| 45 | } | ||
| 46 | |||
| 47 | public function setValue(mixed $object, mixed $value = null): void | ||
| 48 | { | ||
| 49 | $embeddedObject = $this->parentProperty->getValue($object); | ||
| 50 | |||
| 51 | if ($embeddedObject === null) { | ||
| 52 | $this->instantiator ??= new Instantiator(); | ||
| 53 | |||
| 54 | $embeddedObject = $this->instantiator->instantiate($this->embeddedClass); | ||
| 55 | |||
| 56 | $this->parentProperty->setValue($object, $embeddedObject); | ||
| 57 | } | ||
| 58 | |||
| 59 | $this->childProperty->setValue($embeddedObject, $value); | ||
| 60 | } | ||
| 61 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use ReflectionProperty; | ||
| 9 | use ValueError; | ||
| 10 | |||
| 11 | use function array_map; | ||
| 12 | use function is_array; | ||
| 13 | |||
| 14 | /** @deprecated use Doctrine\Persistence\Reflection\EnumReflectionProperty instead */ | ||
| 15 | final class ReflectionEnumProperty extends ReflectionProperty | ||
| 16 | { | ||
| 17 | /** @param class-string<BackedEnum> $enumType */ | ||
| 18 | public function __construct( | ||
| 19 | private readonly ReflectionProperty $originalReflectionProperty, | ||
| 20 | private readonly string $enumType, | ||
| 21 | ) { | ||
| 22 | parent::__construct( | ||
| 23 | $originalReflectionProperty->class, | ||
| 24 | $originalReflectionProperty->name, | ||
| 25 | ); | ||
| 26 | } | ||
| 27 | |||
| 28 | public function getValue(object|null $object = null): int|string|array|null | ||
| 29 | { | ||
| 30 | if ($object === null) { | ||
| 31 | return null; | ||
| 32 | } | ||
| 33 | |||
| 34 | $enum = $this->originalReflectionProperty->getValue($object); | ||
| 35 | |||
| 36 | if ($enum === null) { | ||
| 37 | return null; | ||
| 38 | } | ||
| 39 | |||
| 40 | if (is_array($enum)) { | ||
| 41 | return array_map( | ||
| 42 | static fn (BackedEnum $item): int|string => $item->value, | ||
| 43 | $enum, | ||
| 44 | ); | ||
| 45 | } | ||
| 46 | |||
| 47 | return $enum->value; | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * @param object $object | ||
| 52 | * @param int|string|int[]|string[]|BackedEnum|BackedEnum[]|null $value | ||
| 53 | */ | ||
| 54 | public function setValue(mixed $object, mixed $value = null): void | ||
| 55 | { | ||
| 56 | if ($value !== null) { | ||
| 57 | if (is_array($value)) { | ||
| 58 | $value = array_map(fn (int|string|BackedEnum $item): BackedEnum => $this->initializeEnumValue($object, $item), $value); | ||
| 59 | } else { | ||
| 60 | $value = $this->initializeEnumValue($object, $value); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | $this->originalReflectionProperty->setValue($object, $value); | ||
| 65 | } | ||
| 66 | |||
| 67 | private function initializeEnumValue(object $object, int|string|BackedEnum $value): BackedEnum | ||
| 68 | { | ||
| 69 | if ($value instanceof BackedEnum) { | ||
| 70 | return $value; | ||
| 71 | } | ||
| 72 | |||
| 73 | $enumType = $this->enumType; | ||
| 74 | |||
| 75 | try { | ||
| 76 | return $enumType::from($value); | ||
| 77 | } catch (ValueError $e) { | ||
| 78 | throw MappingException::invalidEnumValue( | ||
| 79 | $object::class, | ||
| 80 | $this->originalReflectionProperty->name, | ||
| 81 | (string) $value, | ||
| 82 | $enumType, | ||
| 83 | $e, | ||
| 84 | ); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use InvalidArgumentException; | ||
| 8 | use LogicException; | ||
| 9 | use ReflectionProperty; | ||
| 10 | |||
| 11 | use function assert; | ||
| 12 | use function func_get_args; | ||
| 13 | use function func_num_args; | ||
| 14 | use function is_object; | ||
| 15 | use function sprintf; | ||
| 16 | |||
| 17 | /** @internal */ | ||
| 18 | final class ReflectionReadonlyProperty extends ReflectionProperty | ||
| 19 | { | ||
| 20 | public function __construct( | ||
| 21 | private readonly ReflectionProperty $wrappedProperty, | ||
| 22 | ) { | ||
| 23 | if (! $wrappedProperty->isReadOnly()) { | ||
| 24 | throw new InvalidArgumentException('Given property is not readonly.'); | ||
| 25 | } | ||
| 26 | |||
| 27 | parent::__construct($wrappedProperty->class, $wrappedProperty->name); | ||
| 28 | } | ||
| 29 | |||
| 30 | public function getValue(object|null $object = null): mixed | ||
| 31 | { | ||
| 32 | return $this->wrappedProperty->getValue(...func_get_args()); | ||
| 33 | } | ||
| 34 | |||
| 35 | public function setValue(mixed $objectOrValue, mixed $value = null): void | ||
| 36 | { | ||
| 37 | if (func_num_args() < 2 || $objectOrValue === null || ! $this->isInitialized($objectOrValue)) { | ||
| 38 | $this->wrappedProperty->setValue(...func_get_args()); | ||
| 39 | |||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | assert(is_object($objectOrValue)); | ||
| 44 | |||
| 45 | if (parent::getValue($objectOrValue) !== $value) { | ||
| 46 | throw new LogicException(sprintf('Attempting to change readonly property %s::$%s.', $this->class, $this->name)); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class SequenceGenerator implements MappingAttribute | ||
| 11 | { | ||
| 12 | public function __construct( | ||
| 13 | public readonly string|null $sequenceName = null, | ||
| 14 | public readonly int $allocationSize = 1, | ||
| 15 | public readonly int $initialValue = 1, | ||
| 16 | ) { | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/Table.php b/vendor/doctrine/orm/src/Mapping/Table.php new file mode 100644 index 0000000..ac1e8ed --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/Table.php | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | use Doctrine\Deprecations\Deprecation; | ||
| 9 | |||
| 10 | #[Attribute(Attribute::TARGET_CLASS)] | ||
| 11 | final class Table implements MappingAttribute | ||
| 12 | { | ||
| 13 | /** | ||
| 14 | * @param array<Index>|null $indexes | ||
| 15 | * @param array<UniqueConstraint>|null $uniqueConstraints | ||
| 16 | * @param array<string,mixed> $options | ||
| 17 | */ | ||
| 18 | public function __construct( | ||
| 19 | public readonly string|null $name = null, | ||
| 20 | public readonly string|null $schema = null, | ||
| 21 | public readonly array|null $indexes = null, | ||
| 22 | public readonly array|null $uniqueConstraints = null, | ||
| 23 | public readonly array $options = [], | ||
| 24 | ) { | ||
| 25 | if ($this->indexes !== null) { | ||
| 26 | Deprecation::trigger( | ||
| 27 | 'doctrine/orm', | ||
| 28 | 'https://github.com/doctrine/orm/pull/11357', | ||
| 29 | '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.', | ||
| 30 | self::class, | ||
| 31 | Index::class, | ||
| 32 | ); | ||
| 33 | } | ||
| 34 | |||
| 35 | if ($this->uniqueConstraints !== null) { | ||
| 36 | Deprecation::trigger( | ||
| 37 | 'doctrine/orm', | ||
| 38 | 'https://github.com/doctrine/orm/pull/11357', | ||
| 39 | '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.', | ||
| 40 | self::class, | ||
| 41 | UniqueConstraint::class, | ||
| 42 | ); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | interface ToManyAssociationMapping | ||
| 8 | { | ||
| 9 | /** @psalm-assert-if-true string $this->indexBy() */ | ||
| 10 | public function isIndexed(): bool; | ||
| 11 | |||
| 12 | public function indexBy(): string; | ||
| 13 | |||
| 14 | /** @return array<string, 'asc'|'desc'> */ | ||
| 15 | public function orderBy(): array; | ||
| 16 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use LogicException; | ||
| 8 | |||
| 9 | use function sprintf; | ||
| 10 | |||
| 11 | /** @internal */ | ||
| 12 | trait ToManyAssociationMappingImplementation | ||
| 13 | { | ||
| 14 | /** | ||
| 15 | * Specification of a field on target-entity that is used to index the | ||
| 16 | * collection by. This field HAS to be either the primary key or a unique | ||
| 17 | * column. Otherwise the collection does not contain all the entities that | ||
| 18 | * are actually related. | ||
| 19 | */ | ||
| 20 | public string|null $indexBy = null; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * A map of field names (of the target entity) to sorting directions | ||
| 24 | * | ||
| 25 | * @var array<string, 'asc'|'desc'> | ||
| 26 | */ | ||
| 27 | public array $orderBy = []; | ||
| 28 | |||
| 29 | /** @return array<string, 'asc'|'desc'> */ | ||
| 30 | final public function orderBy(): array | ||
| 31 | { | ||
| 32 | return $this->orderBy; | ||
| 33 | } | ||
| 34 | |||
| 35 | /** @psalm-assert-if-true !null $this->indexBy */ | ||
| 36 | final public function isIndexed(): bool | ||
| 37 | { | ||
| 38 | return $this->indexBy !== null; | ||
| 39 | } | ||
| 40 | |||
| 41 | final public function indexBy(): string | ||
| 42 | { | ||
| 43 | if (! $this->isIndexed()) { | ||
| 44 | throw new LogicException(sprintf( | ||
| 45 | 'This mapping is not indexed. Use %s::isIndexed() to check that before calling %s.', | ||
| 46 | self::class, | ||
| 47 | __METHOD__, | ||
| 48 | )); | ||
| 49 | } | ||
| 50 | |||
| 51 | return $this->indexBy; | ||
| 52 | } | ||
| 53 | |||
| 54 | /** @return list<string> */ | ||
| 55 | public function __sleep(): array | ||
| 56 | { | ||
| 57 | $serialized = parent::__sleep(); | ||
| 58 | |||
| 59 | if ($this->indexBy !== null) { | ||
| 60 | $serialized[] = 'indexBy'; | ||
| 61 | } | ||
| 62 | |||
| 63 | if ($this->orderBy !== []) { | ||
| 64 | $serialized[] = 'orderBy'; | ||
| 65 | } | ||
| 66 | |||
| 67 | return $serialized; | ||
| 68 | } | ||
| 69 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | abstract class ToManyInverseSideMapping extends InverseSideMapping implements ToManyAssociationMapping | ||
| 8 | { | ||
| 9 | use ToManyAssociationMappingImplementation; | ||
| 10 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ToManyOwningSideMapping.php b/vendor/doctrine/orm/src/Mapping/ToManyOwningSideMapping.php new file mode 100644 index 0000000..92eca7c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToManyOwningSideMapping.php | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | abstract class ToManyOwningSideMapping extends OwningSideMapping | ||
| 8 | { | ||
| 9 | use ToManyAssociationMappingImplementation; | ||
| 10 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ToOneAssociationMapping.php b/vendor/doctrine/orm/src/Mapping/ToOneAssociationMapping.php new file mode 100644 index 0000000..048055c --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToOneAssociationMapping.php | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | interface ToOneAssociationMapping | ||
| 8 | { | ||
| 9 | } | ||
diff --git a/vendor/doctrine/orm/src/Mapping/ToOneInverseSideMapping.php b/vendor/doctrine/orm/src/Mapping/ToOneInverseSideMapping.php new file mode 100644 index 0000000..5be89e6 --- /dev/null +++ b/vendor/doctrine/orm/src/Mapping/ToOneInverseSideMapping.php | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | abstract class ToOneInverseSideMapping extends InverseSideMapping | ||
| 8 | { | ||
| 9 | /** | ||
| 10 | * @param mixed[] $mappingArray | ||
| 11 | * @param class-string $name | ||
| 12 | * @psalm-param array{ | ||
| 13 | * fieldName: string, | ||
| 14 | * sourceEntity: class-string, | ||
| 15 | * targetEntity: class-string, | ||
| 16 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 17 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 18 | * inherited?: class-string|null, | ||
| 19 | * declared?: class-string|null, | ||
| 20 | * cache?: array<mixed>|null, | ||
| 21 | * id?: bool|null, | ||
| 22 | * isOnDeleteCascade?: bool|null, | ||
| 23 | * originalClass?: class-string|null, | ||
| 24 | * originalField?: string|null, | ||
| 25 | * orphanRemoval?: bool, | ||
| 26 | * unique?: bool|null, | ||
| 27 | * joinTable?: mixed[]|null, | ||
| 28 | * type?: int, | ||
| 29 | * isOwningSide: bool, | ||
| 30 | * } $mappingArray | ||
| 31 | */ | ||
| 32 | public static function fromMappingArrayAndName( | ||
| 33 | array $mappingArray, | ||
| 34 | string $name, | ||
| 35 | ): static { | ||
| 36 | $mapping = static::fromMappingArray($mappingArray); | ||
| 37 | |||
| 38 | if (isset($mapping->id) && $mapping->id === true) { | ||
| 39 | throw MappingException::illegalInverseIdentifierAssociation($name, $mapping->fieldName); | ||
| 40 | } | ||
| 41 | |||
| 42 | if ($mapping->orphanRemoval) { | ||
| 43 | if (! $mapping->isCascadeRemove()) { | ||
| 44 | $mapping->cascade[] = 'remove'; | ||
| 45 | } | ||
| 46 | |||
| 47 | $mapping->unique = null; | ||
| 48 | } | ||
| 49 | |||
| 50 | return $mapping; | ||
| 51 | } | ||
| 52 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use RuntimeException; | ||
| 8 | |||
| 9 | use function array_flip; | ||
| 10 | use function assert; | ||
| 11 | use function count; | ||
| 12 | use function trim; | ||
| 13 | |||
| 14 | abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOneAssociationMapping | ||
| 15 | { | ||
| 16 | /** @var array<string, string> */ | ||
| 17 | public array $sourceToTargetKeyColumns = []; | ||
| 18 | |||
| 19 | /** @var array<string, string> */ | ||
| 20 | public array $targetToSourceKeyColumns = []; | ||
| 21 | |||
| 22 | /** @var list<JoinColumnMapping> */ | ||
| 23 | public array $joinColumns = []; | ||
| 24 | |||
| 25 | /** @var array<string, string> */ | ||
| 26 | public array $joinColumnFieldNames = []; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * @param array<string, mixed> $mappingArray | ||
| 30 | * @psalm-param array{ | ||
| 31 | * fieldName: string, | ||
| 32 | * sourceEntity: class-string, | ||
| 33 | * targetEntity: class-string, | ||
| 34 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 35 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 36 | * inherited?: class-string|null, | ||
| 37 | * declared?: class-string|null, | ||
| 38 | * cache?: array<mixed>|null, | ||
| 39 | * id?: bool|null, | ||
| 40 | * isOnDeleteCascade?: bool|null, | ||
| 41 | * originalClass?: class-string|null, | ||
| 42 | * originalField?: string|null, | ||
| 43 | * orphanRemoval?: bool, | ||
| 44 | * unique?: bool|null, | ||
| 45 | * joinTable?: mixed[]|null, | ||
| 46 | * type?: int, | ||
| 47 | * isOwningSide: bool, | ||
| 48 | * joinColumns?: mixed[]|null, | ||
| 49 | * } $mappingArray | ||
| 50 | */ | ||
| 51 | public static function fromMappingArray(array $mappingArray): static | ||
| 52 | { | ||
| 53 | $joinColumns = $mappingArray['joinColumns'] ?? []; | ||
| 54 | unset($mappingArray['joinColumns']); | ||
| 55 | |||
| 56 | $instance = parent::fromMappingArray($mappingArray); | ||
| 57 | assert($instance->isToOneOwningSide()); | ||
| 58 | |||
| 59 | foreach ($joinColumns as $column) { | ||
| 60 | $instance->joinColumns[] = JoinColumnMapping::fromMappingArray($column); | ||
| 61 | } | ||
| 62 | |||
| 63 | if ($instance->orphanRemoval) { | ||
| 64 | if (! $instance->isCascadeRemove()) { | ||
| 65 | $instance->cascade[] = 'remove'; | ||
| 66 | } | ||
| 67 | |||
| 68 | $instance->unique = null; | ||
| 69 | } | ||
| 70 | |||
| 71 | return $instance; | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * @param mixed[] $mappingArray | ||
| 76 | * @param class-string $name | ||
| 77 | * @psalm-param array{ | ||
| 78 | * fieldName: string, | ||
| 79 | * sourceEntity: class-string, | ||
| 80 | * targetEntity: class-string, | ||
| 81 | * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, | ||
| 82 | * fetch?: ClassMetadata::FETCH_*|null, | ||
| 83 | * inherited?: class-string|null, | ||
| 84 | * declared?: class-string|null, | ||
| 85 | * cache?: array<mixed>|null, | ||
| 86 | * id?: bool|null, | ||
| 87 | * isOnDeleteCascade?: bool|null, | ||
| 88 | * originalClass?: class-string|null, | ||
| 89 | * originalField?: string|null, | ||
| 90 | * orphanRemoval?: bool, | ||
| 91 | * unique?: bool|null, | ||
| 92 | * joinTable?: mixed[]|null, | ||
| 93 | * type?: int, | ||
| 94 | * isOwningSide: bool, | ||
| 95 | * joinColumns?: mixed[]|null, | ||
| 96 | * } $mappingArray | ||
| 97 | */ | ||
| 98 | public static function fromMappingArrayAndName( | ||
| 99 | array $mappingArray, | ||
| 100 | NamingStrategy $namingStrategy, | ||
| 101 | string $name, | ||
| 102 | array|null $table, | ||
| 103 | bool $isInheritanceTypeSingleTable, | ||
| 104 | ): static { | ||
| 105 | if (isset($mappingArray['joinColumns'])) { | ||
| 106 | foreach ($mappingArray['joinColumns'] as $index => $joinColumn) { | ||
| 107 | if (empty($joinColumn['name'])) { | ||
| 108 | $mappingArray['joinColumns'][$index]['name'] = $namingStrategy->joinColumnName($mappingArray['fieldName'], $name); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | $mapping = static::fromMappingArray($mappingArray); | ||
| 114 | |||
| 115 | assert($mapping->isToOneOwningSide()); | ||
| 116 | if (empty($mapping->joinColumns)) { | ||
| 117 | // Apply default join column | ||
| 118 | $mapping->joinColumns = [ | ||
| 119 | JoinColumnMapping::fromMappingArray([ | ||
| 120 | 'name' => $namingStrategy->joinColumnName($mapping->fieldName, $name), | ||
| 121 | 'referencedColumnName' => $namingStrategy->referenceColumnName(), | ||
| 122 | ]), | ||
| 123 | ]; | ||
| 124 | } | ||
| 125 | |||
| 126 | $uniqueConstraintColumns = []; | ||
| 127 | |||
| 128 | foreach ($mapping->joinColumns as $joinColumn) { | ||
| 129 | if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) { | ||
| 130 | if (count($mapping->joinColumns) === 1) { | ||
| 131 | if (empty($mapping->id)) { | ||
| 132 | $joinColumn->unique = true; | ||
| 133 | } | ||
| 134 | } else { | ||
| 135 | $uniqueConstraintColumns[] = $joinColumn->name; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | if (empty($joinColumn->referencedColumnName)) { | ||
| 140 | $joinColumn->referencedColumnName = $namingStrategy->referenceColumnName(); | ||
| 141 | } | ||
| 142 | |||
| 143 | if ($joinColumn->name[0] === '`') { | ||
| 144 | $joinColumn->name = trim($joinColumn->name, '`'); | ||
| 145 | $joinColumn->quoted = true; | ||
| 146 | } | ||
| 147 | |||
| 148 | if ($joinColumn->referencedColumnName[0] === '`') { | ||
| 149 | $joinColumn->referencedColumnName = trim($joinColumn->referencedColumnName, '`'); | ||
| 150 | $joinColumn->quoted = true; | ||
| 151 | } | ||
| 152 | |||
| 153 | $mapping->sourceToTargetKeyColumns[$joinColumn->name] = $joinColumn->referencedColumnName; | ||
| 154 | $mapping->joinColumnFieldNames[$joinColumn->name] = $joinColumn->fieldName ?? $joinColumn->name; | ||
| 155 | } | ||
| 156 | |||
| 157 | if ($uniqueConstraintColumns) { | ||
| 158 | if (! $table) { | ||
| 159 | throw new RuntimeException('ClassMetadata::setTable() has to be called before defining a one to one relationship.'); | ||
| 160 | } | ||
| 161 | |||
| 162 | $table['uniqueConstraints'][$mapping->fieldName . '_uniq'] = ['columns' => $uniqueConstraintColumns]; | ||
| 163 | } | ||
| 164 | |||
| 165 | $mapping->targetToSourceKeyColumns = array_flip($mapping->sourceToTargetKeyColumns); | ||
| 166 | |||
| 167 | return $mapping; | ||
| 168 | } | ||
| 169 | |||
| 170 | public function offsetSet(mixed $offset, mixed $value): void | ||
| 171 | { | ||
| 172 | if ($offset === 'joinColumns') { | ||
| 173 | $joinColumns = []; | ||
| 174 | foreach ($value as $column) { | ||
| 175 | $joinColumns[] = JoinColumnMapping::fromMappingArray($column); | ||
| 176 | } | ||
| 177 | |||
| 178 | $this->joinColumns = $joinColumns; | ||
| 179 | |||
| 180 | return; | ||
| 181 | } | ||
| 182 | |||
| 183 | parent::offsetSet($offset, $value); | ||
| 184 | } | ||
| 185 | |||
| 186 | /** @return array<string, mixed> */ | ||
| 187 | public function toArray(): array | ||
| 188 | { | ||
| 189 | $array = parent::toArray(); | ||
| 190 | |||
| 191 | $joinColumns = []; | ||
| 192 | foreach ($array['joinColumns'] as $column) { | ||
| 193 | $joinColumns[] = (array) $column; | ||
| 194 | } | ||
| 195 | |||
| 196 | $array['joinColumns'] = $joinColumns; | ||
| 197 | |||
| 198 | return $array; | ||
| 199 | } | ||
| 200 | |||
| 201 | /** @return list<string> */ | ||
| 202 | public function __sleep(): array | ||
| 203 | { | ||
| 204 | return [ | ||
| 205 | ...parent::__sleep(), | ||
| 206 | 'joinColumns', | ||
| 207 | 'joinColumnFieldNames', | ||
| 208 | 'sourceToTargetKeyColumns', | ||
| 209 | 'targetToSourceKeyColumns', | ||
| 210 | ]; | ||
| 211 | } | ||
| 212 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use BackedEnum; | ||
| 8 | use ReflectionProperty; | ||
| 9 | |||
| 10 | interface TypedFieldMapper | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * Validates & completes the given field mapping based on typed property. | ||
| 14 | * | ||
| 15 | * @param array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} $mapping The field mapping to validate & complete. | ||
| 16 | * | ||
| 17 | * @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping. | ||
| 18 | */ | ||
| 19 | public function validateAndComplete(array $mapping, ReflectionProperty $field): array; | ||
| 20 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use function preg_replace; | ||
| 8 | use function str_contains; | ||
| 9 | use function strrpos; | ||
| 10 | use function strtolower; | ||
| 11 | use function strtoupper; | ||
| 12 | use function substr; | ||
| 13 | |||
| 14 | use const CASE_LOWER; | ||
| 15 | use const CASE_UPPER; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Naming strategy implementing the underscore naming convention. | ||
| 19 | * Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'. | ||
| 20 | * | ||
| 21 | * @link www.doctrine-project.org | ||
| 22 | */ | ||
| 23 | class UnderscoreNamingStrategy implements NamingStrategy | ||
| 24 | { | ||
| 25 | /** | ||
| 26 | * Underscore naming strategy construct. | ||
| 27 | * | ||
| 28 | * @param int $case CASE_LOWER | CASE_UPPER | ||
| 29 | */ | ||
| 30 | public function __construct(private int $case = CASE_LOWER) | ||
| 31 | { | ||
| 32 | } | ||
| 33 | |||
| 34 | /** @return int CASE_LOWER | CASE_UPPER */ | ||
| 35 | public function getCase(): int | ||
| 36 | { | ||
| 37 | return $this->case; | ||
| 38 | } | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Sets string case CASE_LOWER | CASE_UPPER. | ||
| 42 | * Alphabetic characters converted to lowercase or uppercase. | ||
| 43 | */ | ||
| 44 | public function setCase(int $case): void | ||
| 45 | { | ||
| 46 | $this->case = $case; | ||
| 47 | } | ||
| 48 | |||
| 49 | public function classToTableName(string $className): string | ||
| 50 | { | ||
| 51 | if (str_contains($className, '\\')) { | ||
| 52 | $className = substr($className, strrpos($className, '\\') + 1); | ||
| 53 | } | ||
| 54 | |||
| 55 | return $this->underscore($className); | ||
| 56 | } | ||
| 57 | |||
| 58 | public function propertyToColumnName(string $propertyName, string $className): string | ||
| 59 | { | ||
| 60 | return $this->underscore($propertyName); | ||
| 61 | } | ||
| 62 | |||
| 63 | public function embeddedFieldToColumnName( | ||
| 64 | string $propertyName, | ||
| 65 | string $embeddedColumnName, | ||
| 66 | string $className, | ||
| 67 | string $embeddedClassName, | ||
| 68 | ): string { | ||
| 69 | return $this->underscore($propertyName) . '_' . $embeddedColumnName; | ||
| 70 | } | ||
| 71 | |||
| 72 | public function referenceColumnName(): string | ||
| 73 | { | ||
| 74 | return $this->case === CASE_UPPER ? 'ID' : 'id'; | ||
| 75 | } | ||
| 76 | |||
| 77 | public function joinColumnName(string $propertyName, string $className): string | ||
| 78 | { | ||
| 79 | return $this->underscore($propertyName) . '_' . $this->referenceColumnName(); | ||
| 80 | } | ||
| 81 | |||
| 82 | public function joinTableName( | ||
| 83 | string $sourceEntity, | ||
| 84 | string $targetEntity, | ||
| 85 | string $propertyName, | ||
| 86 | ): string { | ||
| 87 | return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity); | ||
| 88 | } | ||
| 89 | |||
| 90 | public function joinKeyColumnName( | ||
| 91 | string $entityName, | ||
| 92 | string|null $referencedColumnName, | ||
| 93 | ): string { | ||
| 94 | return $this->classToTableName($entityName) . '_' . | ||
| 95 | ($referencedColumnName ?: $this->referenceColumnName()); | ||
| 96 | } | ||
| 97 | |||
| 98 | private function underscore(string $string): string | ||
| 99 | { | ||
| 100 | $string = preg_replace('/(?<=[a-z0-9])([A-Z])/', '_$1', $string); | ||
| 101 | |||
| 102 | if ($this->case === CASE_UPPER) { | ||
| 103 | return strtoupper($string); | ||
| 104 | } | ||
| 105 | |||
| 106 | return strtolower($string); | ||
| 107 | } | ||
| 108 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] | ||
| 10 | final class UniqueConstraint implements MappingAttribute | ||
| 11 | { | ||
| 12 | /** | ||
| 13 | * @param array<string>|null $columns | ||
| 14 | * @param array<string>|null $fields | ||
| 15 | * @param array<string,mixed>|null $options | ||
| 16 | */ | ||
| 17 | public function __construct( | ||
| 18 | public readonly string|null $name = null, | ||
| 19 | public readonly array|null $columns = null, | ||
| 20 | public readonly array|null $fields = null, | ||
| 21 | public readonly array|null $options = null, | ||
| 22 | ) { | ||
| 23 | } | ||
| 24 | } | ||
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 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Mapping; | ||
| 6 | |||
| 7 | use Attribute; | ||
| 8 | |||
| 9 | #[Attribute(Attribute::TARGET_PROPERTY)] | ||
| 10 | final class Version implements MappingAttribute | ||
| 11 | { | ||
| 12 | } | ||
