summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Mapping/Driver
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
committerpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
commitbf6655a534a6775d30cafa67bd801276bda1d98d (patch)
treec6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Mapping/Driver
parent94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff)
downloadAppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Mapping/Driver')
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php768
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php146
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/DatabaseDriver.php528
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/ReflectionBasedDriver.php44
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/RepeatableAttributeCollection.php16
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/SimplifiedXmlDriver.php25
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/XmlDriver.php940
7 files changed, 2467 insertions, 0 deletions
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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Doctrine\ORM\Events;
8use Doctrine\ORM\Mapping;
9use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Mapping\MappingException;
12use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
13use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
14use Doctrine\Persistence\Mapping\Driver\MappingDriver;
15use InvalidArgumentException;
16use ReflectionClass;
17use ReflectionMethod;
18use ReflectionProperty;
19
20use function assert;
21use function class_exists;
22use function constant;
23use function defined;
24use function sprintf;
25
26class 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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Attribute;
8use Doctrine\ORM\Mapping\MappingAttribute;
9use LogicException;
10use ReflectionAttribute;
11use ReflectionClass;
12use ReflectionMethod;
13use ReflectionProperty;
14
15use function assert;
16use function is_string;
17use function is_subclass_of;
18use function sprintf;
19
20/** @internal */
21final 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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Doctrine\DBAL\Schema\AbstractSchemaManager;
8use Doctrine\DBAL\Schema\Column;
9use Doctrine\DBAL\Schema\SchemaException;
10use Doctrine\DBAL\Schema\Table;
11use Doctrine\DBAL\Types\Type;
12use Doctrine\DBAL\Types\Types;
13use Doctrine\Inflector\Inflector;
14use Doctrine\Inflector\InflectorFactory;
15use Doctrine\ORM\Mapping\ClassMetadata;
16use Doctrine\ORM\Mapping\MappingException;
17use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
18use Doctrine\Persistence\Mapping\Driver\MappingDriver;
19use InvalidArgumentException;
20use TypeError;
21
22use function array_diff;
23use function array_keys;
24use function array_merge;
25use function assert;
26use function count;
27use function current;
28use function get_debug_type;
29use function in_array;
30use function preg_replace;
31use function sort;
32use function sprintf;
33use function strtolower;
34
35/**
36 * The DatabaseDriver reverse engineers the mapping metadata from a database.
37 *
38 * @link www.doctrine-project.org
39 */
40class 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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Doctrine\ORM\Mapping\ClassMetadata;
8use ReflectionProperty;
9
10/** @internal */
11trait 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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use ArrayObject;
8use Doctrine\ORM\Mapping\MappingAttribute;
9
10/**
11 * @template-extends ArrayObject<int, T>
12 * @template T of MappingAttribute
13 */
14final 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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
8
9/**
10 * XmlDriver that additionally looks for mapping information in a global file.
11 */
12class 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
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Doctrine\Common\Collections\Criteria;
8use Doctrine\Common\Collections\Order;
9use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Mapping\MappingException;
12use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
13use Doctrine\Persistence\Mapping\Driver\FileDriver;
14use Doctrine\Persistence\Mapping\Driver\FileLocator;
15use DOMDocument;
16use InvalidArgumentException;
17use LogicException;
18use SimpleXMLElement;
19
20use function assert;
21use function constant;
22use function count;
23use function defined;
24use function enum_exists;
25use function explode;
26use function extension_loaded;
27use function file_get_contents;
28use function in_array;
29use function libxml_clear_errors;
30use function libxml_get_errors;
31use function libxml_use_internal_errors;
32use function simplexml_load_string;
33use function sprintf;
34use function str_replace;
35use 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 */
44class 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}