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.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 | } | ||