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