Checks if used types are declared or listed as dependencies.
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\ORM\Mapping; |
||
6 | |||
7 | use ArrayIterator; |
||
8 | use Doctrine\ORM\Cache\Exception\CacheException; |
||
9 | use Doctrine\ORM\EntityManagerInterface; |
||
10 | use Doctrine\ORM\Reflection\ReflectionService; |
||
11 | use Doctrine\ORM\Sequencing\Planning\ValueGenerationPlan; |
||
12 | use Doctrine\ORM\Utility\PersisterHelper; |
||
0 ignored issues
–
show
|
|||
13 | use ReflectionException; |
||
14 | use RuntimeException; |
||
15 | use function array_filter; |
||
16 | use function array_merge; |
||
17 | use function class_exists; |
||
18 | use function get_class; |
||
19 | use function in_array; |
||
20 | use function interface_exists; |
||
21 | use function is_subclass_of; |
||
22 | use function method_exists; |
||
23 | use function spl_object_id; |
||
24 | |||
25 | /** |
||
26 | * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata |
||
27 | * of an entity and its associations. |
||
28 | */ |
||
29 | class ClassMetadata extends ComponentMetadata implements TableOwner |
||
30 | { |
||
31 | /** |
||
32 | * The name of the custom repository class used for the entity class. |
||
33 | * (Optional). |
||
34 | * |
||
35 | * @var string |
||
36 | */ |
||
37 | protected $customRepositoryClassName; |
||
38 | |||
39 | /** |
||
40 | * READ-ONLY: Whether this class describes the mapping of a mapped superclass. |
||
41 | * |
||
42 | * @var bool |
||
43 | */ |
||
44 | public $isMappedSuperclass = false; |
||
45 | |||
46 | /** |
||
47 | * READ-ONLY: Whether this class describes the mapping of an embeddable class. |
||
48 | * |
||
49 | * @var bool |
||
50 | */ |
||
51 | public $isEmbeddedClass = false; |
||
52 | |||
53 | /** |
||
54 | * Whether this class describes the mapping of a read-only class. |
||
55 | * That means it is never considered for change-tracking in the UnitOfWork. |
||
56 | * It is a very helpful performance optimization for entities that are immutable, |
||
57 | * either in your domain or through the relation database (coming from a view, |
||
58 | * or a history table for example). |
||
59 | * |
||
60 | * @var bool |
||
61 | */ |
||
62 | private $readOnly = false; |
||
63 | |||
64 | /** |
||
65 | * The names of all subclasses (descendants). |
||
66 | * |
||
67 | * @var string[] |
||
68 | */ |
||
69 | protected $subClasses = []; |
||
70 | |||
71 | /** |
||
72 | * READ-ONLY: The names of all embedded classes based on properties. |
||
73 | * |
||
74 | * @var string[] |
||
75 | */ |
||
76 | //public $embeddedClasses = []; |
||
77 | |||
78 | /** |
||
79 | * READ-ONLY: The registered lifecycle callbacks for entities of this class. |
||
80 | * |
||
81 | * @var string[][] |
||
82 | */ |
||
83 | public $lifecycleCallbacks = []; |
||
84 | |||
85 | /** |
||
86 | * READ-ONLY: The registered entity listeners. |
||
87 | * |
||
88 | * @var mixed[][] |
||
89 | */ |
||
90 | public $entityListeners = []; |
||
91 | |||
92 | /** |
||
93 | * READ-ONLY: The field names of all fields that are part of the identifier/primary key |
||
94 | * of the mapped entity class. |
||
95 | * |
||
96 | * @var string[] |
||
97 | */ |
||
98 | public $identifier = []; |
||
99 | |||
100 | /** |
||
101 | * READ-ONLY: The inheritance mapping type used by the class. |
||
102 | * |
||
103 | * @var string |
||
104 | */ |
||
105 | public $inheritanceType = InheritanceType::NONE; |
||
106 | |||
107 | /** |
||
108 | * READ-ONLY: The policy used for change-tracking on entities of this class. |
||
109 | * |
||
110 | * @var string |
||
111 | */ |
||
112 | public $changeTrackingPolicy = ChangeTrackingPolicy::DEFERRED_IMPLICIT; |
||
113 | |||
114 | /** |
||
115 | * READ-ONLY: The discriminator value of this class. |
||
116 | * |
||
117 | * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies |
||
118 | * where a discriminator column is used.</b> |
||
119 | * |
||
120 | * @see discriminatorColumn |
||
121 | * |
||
122 | * @var mixed |
||
123 | */ |
||
124 | public $discriminatorValue; |
||
125 | |||
126 | /** |
||
127 | * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. |
||
128 | * |
||
129 | * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies |
||
130 | * where a discriminator column is used.</b> |
||
131 | * |
||
132 | * @see discriminatorColumn |
||
133 | * |
||
134 | * @var string[] |
||
135 | */ |
||
136 | public $discriminatorMap = []; |
||
137 | |||
138 | /** |
||
139 | * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE |
||
140 | * inheritance mappings. |
||
141 | * |
||
142 | * @var DiscriminatorColumnMetadata |
||
143 | */ |
||
144 | public $discriminatorColumn; |
||
145 | |||
146 | /** |
||
147 | * READ-ONLY: The primary table metadata. |
||
148 | * |
||
149 | * @var TableMetadata |
||
150 | */ |
||
151 | public $table; |
||
152 | |||
153 | /** |
||
154 | * READ-ONLY: An array of field names. Used to look up field names from column names. |
||
155 | * Keys are column names and values are field names. |
||
156 | * |
||
157 | * @var string[] |
||
158 | */ |
||
159 | public $fieldNames = []; |
||
160 | |||
161 | /** |
||
162 | * READ-ONLY: The field which is used for versioning in optimistic locking (if any). |
||
163 | * |
||
164 | * @var FieldMetadata|null |
||
165 | */ |
||
166 | public $versionProperty; |
||
167 | |||
168 | /** |
||
169 | * Value generation plan is responsible for generating values for auto-generated fields. |
||
170 | * |
||
171 | * @var ValueGenerationPlan |
||
172 | */ |
||
173 | protected $valueGenerationPlan; |
||
174 | |||
175 | /** |
||
176 | * Initializes a new ClassMetadata instance that will hold the object-relational mapping |
||
177 | * metadata of the class with the given name. |
||
178 | * |
||
179 | * @param string $entityName The name of the entity class. |
||
180 | * @param ClassMetadata|null $parent Optional parent class metadata. |
||
181 | */ |
||
182 | 450 | public function __construct(string $entityName, ?ComponentMetadata $parent) |
|
183 | { |
||
184 | 450 | parent::__construct($entityName); |
|
185 | |||
186 | 450 | if ($parent) { |
|
187 | 99 | $this->setParent($parent); |
|
188 | } |
||
189 | 450 | } |
|
190 | |||
191 | /** |
||
192 | * {@inheritdoc} |
||
193 | * |
||
194 | * @throws MappingException |
||
195 | */ |
||
196 | 99 | public function setParent(ComponentMetadata $parent) : void |
|
197 | { |
||
198 | 99 | parent::setParent($parent); |
|
199 | |||
200 | 99 | foreach ($parent->getPropertiesIterator() as $fieldName => $property) { |
|
201 | 96 | $this->addInheritedProperty($property); |
|
202 | } |
||
203 | |||
204 | // @todo guilhermeblanco Assume to be a ClassMetadata temporarily until ClassMetadata split is complete. |
||
205 | /** @var ClassMetadata $parent */ |
||
206 | 99 | $this->setInheritanceType($parent->inheritanceType); |
|
207 | 99 | $this->setIdentifier($parent->identifier); |
|
208 | 99 | $this->setChangeTrackingPolicy($parent->changeTrackingPolicy); |
|
209 | |||
210 | 99 | if ($parent->discriminatorColumn) { |
|
211 | 71 | $this->setDiscriminatorColumn($parent->discriminatorColumn); |
|
212 | 71 | $this->setDiscriminatorMap($parent->discriminatorMap); |
|
213 | } |
||
214 | |||
215 | 99 | if ($parent->isMappedSuperclass) { |
|
216 | 27 | $this->setCustomRepositoryClassName($parent->getCustomRepositoryClassName()); |
|
217 | } |
||
218 | |||
219 | 99 | if ($parent->cache) { |
|
220 | 3 | $this->setCache(clone $parent->cache); |
|
221 | } |
||
222 | |||
223 | 99 | if (! empty($parent->lifecycleCallbacks)) { |
|
224 | 5 | $this->lifecycleCallbacks = $parent->lifecycleCallbacks; |
|
225 | } |
||
226 | |||
227 | 99 | if (! empty($parent->entityListeners)) { |
|
228 | 7 | $this->entityListeners = $parent->entityListeners; |
|
229 | } |
||
230 | 99 | } |
|
231 | |||
232 | public function setClassName(string $className) |
||
233 | { |
||
234 | $this->className = $className; |
||
235 | } |
||
236 | |||
237 | 11 | public function getAncestorsIterator() : ArrayIterator |
|
238 | { |
||
239 | 11 | $ancestors = new ArrayIterator(); |
|
240 | 11 | $parent = $this; |
|
241 | |||
242 | 11 | while (($parent = $parent->parent) !== null) { |
|
243 | 8 | if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) { |
|
244 | 1 | continue; |
|
245 | } |
||
246 | |||
247 | 7 | $ancestors->append($parent); |
|
248 | } |
||
249 | |||
250 | 11 | return $ancestors; |
|
251 | } |
||
252 | |||
253 | 1261 | public function getRootClassName() : string |
|
254 | { |
||
255 | 1261 | return $this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass |
|
256 | 403 | ? $this->parent->getRootClassName() |
|
257 | 1261 | : $this->className; |
|
258 | } |
||
259 | |||
260 | /** |
||
261 | * Handles metadata cloning nicely. |
||
262 | */ |
||
263 | 13 | public function __clone() |
|
264 | { |
||
265 | 13 | if ($this->cache) { |
|
266 | 12 | $this->cache = clone $this->cache; |
|
267 | } |
||
268 | |||
269 | 13 | foreach ($this->properties as $name => $property) { |
|
270 | 13 | $this->properties[$name] = clone $property; |
|
271 | } |
||
272 | 13 | } |
|
273 | |||
274 | /** |
||
275 | * Creates a string representation of this instance. |
||
276 | * |
||
277 | * @return string The string representation of this instance. |
||
278 | * |
||
279 | * @todo Construct meaningful string representation. |
||
280 | */ |
||
281 | public function __toString() |
||
282 | { |
||
283 | return self::class . '@' . spl_object_id($this); |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Determines which fields get serialized. |
||
288 | * |
||
289 | * It is only serialized what is necessary for best unserialization performance. |
||
290 | * That means any metadata properties that are not set or empty or simply have |
||
291 | * their default value are NOT serialized. |
||
292 | * |
||
293 | * Parts that are also NOT serialized because they can not be properly unserialized: |
||
294 | * - reflectionClass |
||
295 | * |
||
296 | * @return string[] The names of all the fields that should be serialized. |
||
297 | */ |
||
298 | 3 | public function __sleep() |
|
299 | { |
||
300 | 3 | $serialized = []; |
|
301 | |||
302 | // This metadata is always serialized/cached. |
||
303 | 3 | $serialized = array_merge($serialized, [ |
|
304 | 3 | 'properties', |
|
305 | 'fieldNames', |
||
306 | //'embeddedClasses', |
||
307 | 'identifier', |
||
308 | 'className', |
||
309 | 'parent', |
||
310 | 'table', |
||
311 | 'valueGenerationPlan', |
||
312 | ]); |
||
313 | |||
314 | // The rest of the metadata is only serialized if necessary. |
||
315 | 3 | if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) { |
|
316 | $serialized[] = 'changeTrackingPolicy'; |
||
317 | } |
||
318 | |||
319 | 3 | if ($this->customRepositoryClassName) { |
|
320 | 1 | $serialized[] = 'customRepositoryClassName'; |
|
321 | } |
||
322 | |||
323 | 3 | if ($this->inheritanceType !== InheritanceType::NONE) { |
|
324 | 1 | $serialized[] = 'inheritanceType'; |
|
325 | 1 | $serialized[] = 'discriminatorColumn'; |
|
326 | 1 | $serialized[] = 'discriminatorValue'; |
|
327 | 1 | $serialized[] = 'discriminatorMap'; |
|
328 | 1 | $serialized[] = 'subClasses'; |
|
329 | } |
||
330 | |||
331 | 3 | if ($this->isMappedSuperclass) { |
|
332 | $serialized[] = 'isMappedSuperclass'; |
||
333 | } |
||
334 | |||
335 | 3 | if ($this->isEmbeddedClass) { |
|
336 | $serialized[] = 'isEmbeddedClass'; |
||
337 | } |
||
338 | |||
339 | 3 | if ($this->isVersioned()) { |
|
340 | $serialized[] = 'versionProperty'; |
||
341 | } |
||
342 | |||
343 | 3 | if ($this->lifecycleCallbacks) { |
|
344 | $serialized[] = 'lifecycleCallbacks'; |
||
345 | } |
||
346 | |||
347 | 3 | if ($this->entityListeners) { |
|
348 | 1 | $serialized[] = 'entityListeners'; |
|
349 | } |
||
350 | |||
351 | 3 | if ($this->cache) { |
|
352 | $serialized[] = 'cache'; |
||
353 | } |
||
354 | |||
355 | 3 | if ($this->readOnly) { |
|
356 | 1 | $serialized[] = 'readOnly'; |
|
357 | } |
||
358 | |||
359 | 3 | return $serialized; |
|
360 | } |
||
361 | |||
362 | /** |
||
363 | * Sets the change tracking policy used by this class. |
||
364 | */ |
||
365 | 105 | public function setChangeTrackingPolicy(string $policy) : void |
|
366 | { |
||
367 | 105 | $this->changeTrackingPolicy = $policy; |
|
368 | 105 | } |
|
369 | |||
370 | /** |
||
371 | * Checks whether a field is part of the identifier/primary key field(s). |
||
372 | * |
||
373 | * @param string $fieldName The field name. |
||
374 | * |
||
375 | * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise. |
||
376 | */ |
||
377 | 1028 | public function isIdentifier(string $fieldName) : bool |
|
378 | { |
||
379 | 1028 | if (! $this->identifier) { |
|
380 | 1 | return false; |
|
381 | } |
||
382 | |||
383 | 1027 | if (! $this->isIdentifierComposite()) { |
|
384 | 1023 | return $fieldName === $this->identifier[0]; |
|
385 | } |
||
386 | |||
387 | 93 | return in_array($fieldName, $this->identifier, true); |
|
388 | } |
||
389 | |||
390 | 1214 | public function isIdentifierComposite() : bool |
|
391 | { |
||
392 | 1214 | return isset($this->identifier[1]); |
|
393 | } |
||
394 | |||
395 | /** |
||
396 | * Validates Identifier. |
||
397 | * |
||
398 | * @throws MappingException |
||
399 | */ |
||
400 | 368 | public function validateIdentifier() : void |
|
401 | { |
||
402 | 368 | if ($this->isMappedSuperclass || $this->isEmbeddedClass) { |
|
403 | 27 | return; |
|
404 | } |
||
405 | |||
406 | // Verify & complete identifier mapping |
||
407 | 368 | if (! $this->identifier) { |
|
408 | 2 | throw MappingException::identifierRequired($this->className); |
|
409 | } |
||
410 | |||
411 | $explicitlyGeneratedProperties = array_filter($this->properties, static function (Property $property) : bool { |
||
412 | 366 | return $property instanceof FieldMetadata |
|
413 | 366 | && $property->isPrimaryKey() |
|
414 | 366 | && $property->hasValueGenerator(); |
|
415 | 366 | }); |
|
416 | |||
417 | 366 | if ($explicitlyGeneratedProperties && $this->isIdentifierComposite()) { |
|
418 | throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className); |
||
419 | } |
||
420 | 366 | } |
|
421 | |||
422 | /** |
||
423 | * Validates lifecycle callbacks. |
||
424 | * |
||
425 | * @throws MappingException |
||
426 | */ |
||
427 | 369 | public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void |
|
428 | { |
||
429 | 369 | foreach ($this->lifecycleCallbacks as $callbacks) { |
|
430 | /** @var array $callbacks */ |
||
431 | 10 | foreach ($callbacks as $callbackFuncName) { |
|
432 | 10 | if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) { |
|
433 | 1 | throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName); |
|
434 | } |
||
435 | } |
||
436 | } |
||
437 | 368 | } |
|
438 | |||
439 | /** |
||
440 | * {@inheritDoc} |
||
441 | */ |
||
442 | 402 | public function getIdentifierFieldNames() |
|
443 | { |
||
444 | 402 | return $this->identifier; |
|
445 | } |
||
446 | |||
447 | /** |
||
448 | * Gets the name of the single id field. Note that this only works on |
||
449 | * entity classes that have a single-field pk. |
||
450 | * |
||
451 | * @return string |
||
452 | * |
||
453 | * @throws MappingException If the class has a composite primary key. |
||
454 | */ |
||
455 | 151 | public function getSingleIdentifierFieldName() |
|
456 | { |
||
457 | 151 | if ($this->isIdentifierComposite()) { |
|
458 | 1 | throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className); |
|
459 | } |
||
460 | |||
461 | 150 | if (! isset($this->identifier[0])) { |
|
462 | 1 | throw MappingException::noIdDefined($this->className); |
|
463 | } |
||
464 | |||
465 | 149 | return $this->identifier[0]; |
|
466 | } |
||
467 | |||
468 | /** |
||
469 | * INTERNAL: |
||
470 | * Sets the mapped identifier/primary key fields of this class. |
||
471 | * Mainly used by the ClassMetadataFactory to assign inherited identifiers. |
||
472 | * |
||
473 | * @param mixed[] $identifier |
||
474 | */ |
||
475 | 101 | public function setIdentifier(array $identifier) |
|
476 | { |
||
477 | 101 | $this->identifier = $identifier; |
|
478 | 101 | } |
|
479 | |||
480 | /** |
||
481 | * {@inheritDoc} |
||
482 | */ |
||
483 | 1054 | public function getIdentifier() |
|
484 | { |
||
485 | 1054 | return $this->identifier; |
|
486 | } |
||
487 | |||
488 | /** |
||
489 | * {@inheritDoc} |
||
490 | */ |
||
491 | 189 | public function hasField($fieldName) |
|
492 | { |
||
493 | 189 | return isset($this->properties[$fieldName]) |
|
494 | 189 | && $this->properties[$fieldName] instanceof FieldMetadata; |
|
495 | } |
||
496 | |||
497 | /** |
||
498 | * Returns an array with identifier column names and their corresponding ColumnMetadata. |
||
499 | * |
||
500 | * @return ColumnMetadata[] |
||
501 | */ |
||
502 | 442 | public function getIdentifierColumns(EntityManagerInterface $em) : array |
|
503 | { |
||
504 | 442 | $columns = []; |
|
505 | |||
506 | 442 | foreach ($this->identifier as $idProperty) { |
|
507 | 442 | $property = $this->getProperty($idProperty); |
|
508 | |||
509 | 442 | if ($property instanceof FieldMetadata) { |
|
510 | 436 | $columns[$property->getColumnName()] = $property; |
|
511 | |||
512 | 436 | continue; |
|
513 | } |
||
514 | |||
515 | /** @var AssociationMetadata $property */ |
||
516 | |||
517 | // Association defined as Id field |
||
518 | 25 | $targetClass = $em->getClassMetadata($property->getTargetEntity()); |
|
519 | |||
520 | 25 | if (! $property->isOwningSide()) { |
|
521 | $property = $targetClass->getProperty($property->getMappedBy()); |
||
522 | } |
||
523 | |||
524 | 25 | $joinColumns = $property instanceof ManyToManyAssociationMetadata |
|
525 | ? $property->getJoinTable()->getInverseJoinColumns() |
||
526 | 25 | : $property->getJoinColumns(); |
|
527 | |||
528 | 25 | foreach ($joinColumns as $joinColumn) { |
|
529 | 25 | $columns[$joinColumn->getColumnName()] = $joinColumn; |
|
530 | } |
||
531 | } |
||
532 | |||
533 | 442 | return $columns; |
|
534 | } |
||
535 | |||
536 | /** |
||
537 | * Gets the name of the primary table. |
||
538 | */ |
||
539 | 1575 | public function getTableName() : ?string |
|
540 | { |
||
541 | 1575 | return $this->table->getName(); |
|
542 | } |
||
543 | |||
544 | /** |
||
545 | * Gets primary table's schema name. |
||
546 | */ |
||
547 | 23 | public function getSchemaName() : ?string |
|
548 | { |
||
549 | 23 | return $this->table->getSchema(); |
|
550 | } |
||
551 | |||
552 | /** |
||
553 | * Gets the table name to use for temporary identifier tables of this class. |
||
554 | */ |
||
555 | 7 | public function getTemporaryIdTableName() : string |
|
556 | { |
||
557 | 7 | $schema = $this->getSchemaName() === null |
|
558 | 6 | ? '' |
|
559 | 7 | : $this->getSchemaName() . '_'; |
|
560 | |||
561 | // replace dots with underscores because PostgreSQL creates temporary tables in a special schema |
||
562 | 7 | return $schema . $this->getTableName() . '_id_tmp'; |
|
563 | } |
||
564 | |||
565 | /** |
||
566 | * Sets the mapped subclasses of this class. |
||
567 | * |
||
568 | * @param string[] $subclasses The names of all mapped subclasses. |
||
569 | * |
||
570 | * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible! |
||
571 | */ |
||
572 | 4 | public function setSubclasses(array $subclasses) : void |
|
573 | { |
||
574 | 4 | foreach ($subclasses as $subclass) { |
|
575 | 3 | $this->subClasses[] = $subclass; |
|
576 | } |
||
577 | 4 | } |
|
578 | |||
579 | /** |
||
580 | * @return string[] |
||
581 | */ |
||
582 | 1081 | public function getSubClasses() : array |
|
583 | { |
||
584 | 1081 | return $this->subClasses; |
|
585 | } |
||
586 | |||
587 | /** |
||
588 | * Sets the inheritance type used by the class and its subclasses. |
||
589 | * |
||
590 | * @param int $type |
||
591 | * |
||
592 | * @throws MappingException |
||
593 | */ |
||
594 | 121 | public function setInheritanceType($type) : void |
|
595 | { |
||
596 | 121 | if (! $this->isInheritanceType($type)) { |
|
597 | throw MappingException::invalidInheritanceType($this->className, $type); |
||
598 | } |
||
599 | |||
600 | 121 | $this->inheritanceType = $type; |
|
601 | 121 | } |
|
602 | |||
603 | /** |
||
604 | * Sets the override property mapping for an entity relationship. |
||
605 | * |
||
606 | * @throws RuntimeException |
||
607 | * @throws MappingException |
||
608 | * @throws CacheException |
||
609 | */ |
||
610 | 12 | public function setPropertyOverride(Property $property) : void |
|
611 | { |
||
612 | 12 | $fieldName = $property->getName(); |
|
613 | |||
614 | 12 | if (! isset($this->properties[$fieldName])) { |
|
615 | 2 | throw MappingException::invalidOverrideFieldName($this->className, $fieldName); |
|
616 | } |
||
617 | |||
618 | 10 | $originalProperty = $this->getProperty($fieldName); |
|
619 | 10 | $originalPropertyClassName = get_class($originalProperty); |
|
620 | |||
621 | // If moving from transient to persistent, assume it's a new property |
||
622 | 10 | if ($originalPropertyClassName === TransientMetadata::class) { |
|
623 | 1 | unset($this->properties[$fieldName]); |
|
624 | |||
625 | 1 | $this->addProperty($property); |
|
626 | |||
627 | 1 | return; |
|
628 | } |
||
629 | |||
630 | // Do not allow to change property type |
||
631 | 9 | if ($originalPropertyClassName !== get_class($property)) { |
|
632 | throw MappingException::invalidOverridePropertyType($this->className, $fieldName); |
||
633 | } |
||
634 | |||
635 | // Do not allow to change version property |
||
636 | 9 | if ($originalProperty instanceof FieldMetadata && $originalProperty->isVersioned()) { |
|
637 | throw MappingException::invalidOverrideVersionField($this->className, $fieldName); |
||
638 | } |
||
639 | |||
640 | 9 | unset($this->properties[$fieldName]); |
|
641 | |||
642 | 9 | if ($property instanceof FieldMetadata) { |
|
643 | // Unset defined fieldName prior to override |
||
644 | 5 | unset($this->fieldNames[$originalProperty->getColumnName()]); |
|
645 | |||
646 | // Revert what should not be allowed to change |
||
647 | 5 | $property->setDeclaringClass($originalProperty->getDeclaringClass()); |
|
648 | 5 | $property->setPrimaryKey($originalProperty->isPrimaryKey()); |
|
649 | 9 | } elseif ($property instanceof AssociationMetadata) { |
|
650 | // Unset all defined fieldNames prior to override |
||
651 | 9 | if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) { |
|
652 | 5 | foreach ($originalProperty->getJoinColumns() as $joinColumn) { |
|
653 | 5 | unset($this->fieldNames[$joinColumn->getColumnName()]); |
|
654 | } |
||
655 | } |
||
656 | |||
657 | // Override what it should be allowed to change |
||
658 | 9 | if ($property->getInversedBy()) { |
|
659 | 8 | $originalProperty->setInversedBy($property->getInversedBy()); |
|
660 | } |
||
661 | |||
662 | 9 | if ($property->getFetchMode() !== $originalProperty->getFetchMode()) { |
|
663 | 2 | $originalProperty->setFetchMode($property->getFetchMode()); |
|
664 | } |
||
665 | |||
666 | 9 | if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) { |
|
667 | 5 | $originalProperty->setJoinColumns($property->getJoinColumns()); |
|
668 | 8 | } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) { |
|
669 | 8 | $originalProperty->setJoinTable($property->getJoinTable()); |
|
670 | } |
||
671 | |||
672 | 9 | $property = $originalProperty; |
|
673 | } |
||
674 | |||
675 | 9 | $this->addProperty($property); |
|
676 | 9 | } |
|
677 | |||
678 | /** |
||
679 | * Checks if this entity is the root in any entity-inheritance-hierarchy. |
||
680 | * |
||
681 | * @return bool |
||
682 | */ |
||
683 | 337 | public function isRootEntity() |
|
684 | { |
||
685 | 337 | return $this->className === $this->getRootClassName(); |
|
686 | } |
||
687 | |||
688 | /** |
||
689 | * Checks whether a mapped field is inherited from a superclass. |
||
690 | * |
||
691 | * @param string $fieldName |
||
692 | * |
||
693 | * @return bool TRUE if the field is inherited, FALSE otherwise. |
||
694 | */ |
||
695 | 623 | public function isInheritedProperty($fieldName) |
|
696 | { |
||
697 | 623 | $declaringClass = $this->properties[$fieldName]->getDeclaringClass(); |
|
698 | |||
699 | 623 | return $declaringClass->className !== $this->className; |
|
700 | } |
||
701 | |||
702 | /** |
||
703 | * {@inheritdoc} |
||
704 | */ |
||
705 | 431 | public function setTable(TableMetadata $table) : void |
|
706 | { |
||
707 | 431 | $this->table = $table; |
|
708 | |||
709 | // Make sure inherited and declared properties reflect newly defined table |
||
710 | 431 | foreach ($this->properties as $property) { |
|
711 | switch (true) { |
||
712 | 96 | case $property instanceof FieldMetadata: |
|
713 | 96 | $property->setTableName($property->getTableName() ?? $table->getName()); |
|
714 | 96 | break; |
|
715 | |||
716 | 42 | case $property instanceof ToOneAssociationMetadata: |
|
717 | // Resolve association join column table names |
||
718 | 34 | foreach ($property->getJoinColumns() as $joinColumn) { |
|
719 | /** @var JoinColumnMetadata $joinColumn */ |
||
720 | 34 | $joinColumn->setTableName($joinColumn->getTableName() ?? $table->getName()); |
|
721 | } |
||
722 | |||
723 | 34 | break; |
|
724 | } |
||
725 | } |
||
726 | 431 | } |
|
727 | |||
728 | /** |
||
729 | * Checks whether the given type identifies an inheritance type. |
||
730 | * |
||
731 | * @param int $type |
||
732 | * |
||
733 | * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise. |
||
734 | */ |
||
735 | 121 | private function isInheritanceType($type) |
|
736 | { |
||
737 | 121 | return $type === InheritanceType::NONE |
|
738 | 94 | || $type === InheritanceType::SINGLE_TABLE |
|
739 | 52 | || $type === InheritanceType::JOINED |
|
740 | 121 | || $type === InheritanceType::TABLE_PER_CLASS; |
|
741 | } |
||
742 | |||
743 | 940 | public function getColumn(string $columnName) : ?ColumnMetadata |
|
744 | { |
||
745 | 940 | foreach ($this->properties as $property) { |
|
746 | switch (true) { |
||
747 | 940 | case $property instanceof FieldMetadata: |
|
748 | 940 | if ($property->getColumnName() === $columnName) { |
|
749 | 940 | return $property; |
|
750 | } |
||
751 | |||
752 | 15 | break; |
|
753 | |||
754 | 24 | case $property instanceof ToOneAssociationMetadata: |
|
755 | 12 | foreach ($property->getJoinColumns() as $joinColumn) { |
|
756 | 12 | if ($joinColumn->getColumnName() === $columnName) { |
|
757 | 12 | return $joinColumn; |
|
758 | } |
||
759 | } |
||
760 | |||
761 | 4 | break; |
|
762 | } |
||
763 | } |
||
764 | |||
765 | return null; |
||
766 | } |
||
767 | |||
768 | /** |
||
769 | * Add a property mapping. |
||
770 | * |
||
771 | * @throws RuntimeException |
||
772 | * @throws MappingException |
||
773 | * @throws CacheException |
||
774 | * @throws ReflectionException |
||
775 | */ |
||
776 | 418 | public function addProperty(Property $property) : void |
|
777 | { |
||
778 | 418 | $fieldName = $property->getName(); |
|
779 | |||
780 | // Check for empty field name |
||
781 | 418 | if (empty($fieldName)) { |
|
782 | 1 | throw MappingException::missingFieldName($this->className); |
|
783 | } |
||
784 | |||
785 | 417 | $property->setDeclaringClass($this); |
|
786 | |||
787 | switch (true) { |
||
788 | 417 | case $property instanceof FieldMetadata: |
|
789 | 407 | if ($property->isVersioned()) { |
|
790 | 20 | $this->versionProperty = $property; |
|
791 | } |
||
792 | |||
793 | 407 | $this->fieldNames[$property->getColumnName()] = $property->getName(); |
|
794 | 407 | break; |
|
795 | |||
796 | 276 | case $property instanceof ToOneAssociationMetadata: |
|
797 | 241 | foreach ($property->getJoinColumns() as $joinColumnMetadata) { |
|
798 | 233 | $this->fieldNames[$joinColumnMetadata->getColumnName()] = $property->getName(); |
|
799 | } |
||
800 | |||
801 | 241 | break; |
|
802 | |||
803 | default: |
||
804 | // Transient properties are ignored on purpose here! =) |
||
805 | 179 | break; |
|
806 | } |
||
807 | |||
808 | 417 | if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) { |
|
809 | 396 | $this->identifier[] = $fieldName; |
|
810 | } |
||
811 | |||
812 | 417 | parent::addProperty($property); |
|
813 | 417 | } |
|
814 | |||
815 | /** |
||
816 | * INTERNAL: |
||
817 | * Adds a property mapping without completing/validating it. |
||
818 | * This is mainly used to add inherited property mappings to derived classes. |
||
819 | */ |
||
820 | 97 | public function addInheritedProperty(Property $property) |
|
821 | { |
||
822 | 97 | if (isset($this->properties[$property->getName()])) { |
|
823 | 1 | throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName())); |
|
824 | } |
||
825 | |||
826 | 97 | $declaringClass = $property->getDeclaringClass(); |
|
827 | 97 | $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property; |
|
828 | |||
829 | 97 | if ($inheritedProperty instanceof FieldMetadata) { |
|
830 | 96 | if (! $declaringClass->isMappedSuperclass) { |
|
831 | 74 | $inheritedProperty->setTableName($property->getTableName()); |
|
832 | } |
||
833 | |||
834 | 96 | if ($inheritedProperty->isVersioned()) { |
|
835 | 4 | $this->versionProperty = $inheritedProperty; |
|
836 | } |
||
837 | |||
838 | 96 | $this->fieldNames[$property->getColumnName()] = $property->getName(); |
|
839 | 43 | } elseif ($inheritedProperty instanceof AssociationMetadata) { |
|
840 | 42 | if ($declaringClass->isMappedSuperclass) { |
|
841 | 10 | $inheritedProperty->setSourceEntity($this->className); |
|
842 | } |
||
843 | |||
844 | // Need to add inherited fieldNames |
||
845 | 42 | if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) { |
|
846 | 35 | foreach ($inheritedProperty->getJoinColumns() as $joinColumn) { |
|
847 | /** @var JoinColumnMetadata $joinColumn */ |
||
848 | 34 | $this->fieldNames[$joinColumn->getColumnName()] = $property->getName(); |
|
849 | } |
||
850 | } |
||
851 | } |
||
852 | |||
853 | 97 | $this->properties[$property->getName()] = $inheritedProperty; |
|
854 | 97 | } |
|
855 | |||
856 | /** |
||
857 | * Registers a custom repository class for the entity class. |
||
858 | * |
||
859 | * @param string|null $repositoryClassName The class name of the custom mapper. |
||
860 | */ |
||
861 | 30 | public function setCustomRepositoryClassName(?string $repositoryClassName) |
|
862 | { |
||
863 | 30 | $this->customRepositoryClassName = $repositoryClassName; |
|
864 | 30 | } |
|
865 | |||
866 | 164 | public function getCustomRepositoryClassName() : ?string |
|
867 | { |
||
868 | 164 | return $this->customRepositoryClassName; |
|
869 | } |
||
870 | |||
871 | /** |
||
872 | * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. |
||
873 | * |
||
874 | * @param string $lifecycleEvent |
||
875 | * |
||
876 | * @return bool |
||
877 | */ |
||
878 | public function hasLifecycleCallbacks($lifecycleEvent) |
||
879 | { |
||
880 | return isset($this->lifecycleCallbacks[$lifecycleEvent]); |
||
881 | } |
||
882 | |||
883 | /** |
||
884 | * Gets the registered lifecycle callbacks for an event. |
||
885 | * |
||
886 | * @param string $event |
||
887 | * |
||
888 | * @return string[] |
||
889 | */ |
||
890 | public function getLifecycleCallbacks($event) : array |
||
891 | { |
||
892 | return $this->lifecycleCallbacks[$event] ?? []; |
||
893 | } |
||
894 | |||
895 | /** |
||
896 | * Adds a lifecycle callback for entities of this class. |
||
897 | */ |
||
898 | 16 | public function addLifecycleCallback(string $eventName, string $methodName) |
|
899 | { |
||
900 | 16 | if (in_array($methodName, $this->lifecycleCallbacks[$eventName] ?? [], true)) { |
|
901 | 3 | return; |
|
902 | } |
||
903 | |||
904 | 16 | $this->lifecycleCallbacks[$eventName][] = $methodName; |
|
905 | 16 | } |
|
906 | |||
907 | /** |
||
908 | * Adds a entity listener for entities of this class. |
||
909 | * |
||
910 | * @param string $eventName The entity lifecycle event. |
||
911 | * @param string $class The listener class. |
||
912 | * @param string $method The listener callback method. |
||
913 | * |
||
914 | * @throws MappingException |
||
915 | */ |
||
916 | 13 | public function addEntityListener(string $eventName, string $class, string $methodName) : void |
|
917 | { |
||
918 | $listener = [ |
||
919 | 13 | 'class' => $class, |
|
920 | 13 | 'method' => $methodName, |
|
921 | ]; |
||
922 | |||
923 | 13 | if (! class_exists($class)) { |
|
924 | 1 | throw MappingException::entityListenerClassNotFound($class, $this->className); |
|
925 | } |
||
926 | |||
927 | 12 | if (! method_exists($class, $methodName)) { |
|
928 | 1 | throw MappingException::entityListenerMethodNotFound($class, $methodName, $this->className); |
|
929 | } |
||
930 | |||
931 | // Check if entity listener already got registered and ignore it if positive |
||
932 | 11 | if (in_array($listener, $this->entityListeners[$eventName] ?? [], true)) { |
|
933 | 5 | return; |
|
934 | } |
||
935 | |||
936 | 11 | $this->entityListeners[$eventName][] = $listener; |
|
937 | 11 | } |
|
938 | |||
939 | /** |
||
940 | * Sets the discriminator column definition. |
||
941 | * |
||
942 | * @see getDiscriminatorColumn() |
||
943 | * |
||
944 | * @throws MappingException |
||
945 | */ |
||
946 | 95 | public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void |
|
947 | { |
||
948 | 95 | if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) { |
|
949 | throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName()); |
||
950 | } |
||
951 | |||
952 | 95 | $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName()); |
|
953 | |||
954 | 95 | $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date']; |
|
955 | |||
956 | 95 | if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) { |
|
957 | throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName()); |
||
958 | } |
||
959 | |||
960 | 95 | $this->discriminatorColumn = $discriminatorColumn; |
|
961 | 95 | } |
|
962 | |||
963 | /** |
||
964 | * Sets the discriminator values used by this class. |
||
965 | * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. |
||
966 | * |
||
967 | * @param string[] $map |
||
968 | * |
||
969 | * @throws MappingException |
||
970 | */ |
||
971 | 90 | public function setDiscriminatorMap(array $map) : void |
|
972 | { |
||
973 | 90 | foreach ($map as $value => $className) { |
|
974 | 90 | $this->addDiscriminatorMapClass($value, $className); |
|
975 | } |
||
976 | 90 | } |
|
977 | |||
978 | /** |
||
979 | * Adds one entry of the discriminator map with a new class and corresponding name. |
||
980 | * |
||
981 | * @param string|int $name |
||
982 | * |
||
983 | * @throws MappingException |
||
984 | */ |
||
985 | 90 | public function addDiscriminatorMapClass($name, string $className) : void |
|
986 | { |
||
987 | 90 | $this->discriminatorMap[$name] = $className; |
|
988 | |||
989 | 90 | if ($this->className === $className) { |
|
990 | 76 | $this->discriminatorValue = $name; |
|
991 | |||
992 | 76 | return; |
|
993 | } |
||
994 | |||
995 | 89 | if (! (class_exists($className) || interface_exists($className))) { |
|
996 | throw MappingException::invalidClassInDiscriminatorMap($className, $this->className); |
||
997 | } |
||
998 | |||
999 | 89 | if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) { |
|
1000 | 84 | $this->subClasses[] = $className; |
|
1001 | } |
||
1002 | 89 | } |
|
1003 | |||
1004 | 1032 | public function getValueGenerationPlan() : ValueGenerationPlan |
|
1005 | { |
||
1006 | 1032 | return $this->valueGenerationPlan; |
|
1007 | } |
||
1008 | |||
1009 | 368 | public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void |
|
1010 | { |
||
1011 | 368 | $this->valueGenerationPlan = $valueGenerationPlan; |
|
1012 | 368 | } |
|
1013 | |||
1014 | 399 | public function checkPropertyDuplication(string $columnName) : bool |
|
1015 | { |
||
1016 | 399 | return isset($this->fieldNames[$columnName]) |
|
1017 | 399 | || ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName); |
|
1018 | } |
||
1019 | |||
1020 | /** |
||
1021 | * Marks this class as read only, no change tracking is applied to it. |
||
1022 | */ |
||
1023 | 2 | public function asReadOnly() : void |
|
1024 | { |
||
1025 | 2 | $this->readOnly = true; |
|
1026 | 2 | } |
|
1027 | |||
1028 | /** |
||
1029 | * Whether this class is read only or not. |
||
1030 | */ |
||
1031 | 447 | public function isReadOnly() : bool |
|
1032 | { |
||
1033 | 447 | return $this->readOnly; |
|
1034 | } |
||
1035 | |||
1036 | 1092 | public function isVersioned() : bool |
|
1037 | { |
||
1038 | 1092 | return $this->versionProperty !== null; |
|
1039 | } |
||
1040 | |||
1041 | /** |
||
1042 | * Map Embedded Class |
||
1043 | * |
||
1044 | * @param mixed[] $mapping |
||
1045 | * |
||
1046 | * @throws MappingException |
||
1047 | */ |
||
1048 | public function mapEmbedded(array $mapping) : void |
||
1049 | { |
||
1050 | /*if (isset($this->properties[$mapping['fieldName']])) { |
||
1051 | throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName'])); |
||
1052 | } |
||
1053 | |||
1054 | $this->embeddedClasses[$mapping['fieldName']] = [ |
||
1055 | 'class' => $this->fullyQualifiedClassName($mapping['class']), |
||
1056 | 'columnPrefix' => $mapping['columnPrefix'], |
||
1057 | 'declaredField' => $mapping['declaredField'] ?? null, |
||
1058 | 'originalField' => $mapping['originalField'] ?? null, |
||
1059 | 'declaringClass' => $this, |
||
1060 | ];*/ |
||
1061 | } |
||
1062 | |||
1063 | /** |
||
1064 | * Inline the embeddable class |
||
1065 | * |
||
1066 | * @param string $property |
||
1067 | */ |
||
1068 | public function inlineEmbeddable($property, ClassMetadata $embeddable) : void |
||
1069 | { |
||
1070 | /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) { |
||
1071 | $fieldMapping['fieldName'] = $property . "." . $fieldName; |
||
1072 | $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName(); |
||
1073 | $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName; |
||
1074 | $fieldMapping['declaredField'] = isset($fieldMapping['declaredField']) |
||
1075 | ? $property . '.' . $fieldMapping['declaredField'] |
||
1076 | : $property; |
||
1077 | |||
1078 | if (! empty($this->embeddedClasses[$property]['columnPrefix'])) { |
||
1079 | $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName']; |
||
1080 | } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) { |
||
1081 | $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName( |
||
1082 | $property, |
||
1083 | $fieldMapping['columnName'], |
||
1084 | $this->reflectionClass->getName(), |
||
1085 | $embeddable->reflectionClass->getName() |
||
1086 | ); |
||
1087 | } |
||
1088 | |||
1089 | $this->mapField($fieldMapping); |
||
1090 | }*/ |
||
1091 | } |
||
1092 | } |
||
1093 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths