Failed Conditions
Push — master ( e747f7...5b15a6 )
by Guilherme
19:57
created

lib/Doctrine/ORM/Mapping/ClassMetadata.php (2 issues)

Labels
Severity
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\Cache\Exception\NonCacheableEntityAssociation;
10
use Doctrine\ORM\EntityManagerInterface;
11
use Doctrine\ORM\Mapping\Factory\NamingStrategy;
12
use Doctrine\ORM\Reflection\ReflectionService;
13
use Doctrine\ORM\Sequencing\Planning\ValueGenerationPlan;
14
use Doctrine\ORM\Utility\PersisterHelper;
15
use RuntimeException;
16
use function array_diff;
17
use function array_filter;
18
use function array_intersect;
19
use function array_map;
20
use function array_merge;
21
use function class_exists;
22
use function count;
23
use function get_class;
24
use function in_array;
25
use function interface_exists;
26
use function is_subclass_of;
27
use function method_exists;
28
use function spl_object_id;
29
use function sprintf;
30
31
/**
32
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
33
 * of an entity and its associations.
34
 */
35
class ClassMetadata extends ComponentMetadata implements TableOwner
36
{
37
    /**
38
     * The name of the custom repository class used for the entity class.
39
     * (Optional).
40
     *
41
     * @var string
42
     */
43
    protected $customRepositoryClassName;
44
45
    /**
46
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
47
     *
48
     * @var bool
49
     */
50
    public $isMappedSuperclass = false;
51
52
    /**
53
     * READ-ONLY: Whether this class describes the mapping of an embeddable class.
54
     *
55
     * @var bool
56
     */
57
    public $isEmbeddedClass = false;
58
59
    /**
60
     * Whether this class describes the mapping of a read-only class.
61
     * That means it is never considered for change-tracking in the UnitOfWork.
62
     * It is a very helpful performance optimization for entities that are immutable,
63
     * either in your domain or through the relation database (coming from a view,
64
     * or a history table for example).
65
     *
66
     * @var bool
67
     */
68
    private $readOnly = false;
69
70
    /**
71
     * The names of all subclasses (descendants).
72
     *
73
     * @var string[]
74
     */
75
    protected $subClasses = [];
76
77
    /**
78
     * READ-ONLY: The names of all embedded classes based on properties.
79
     *
80
     * @var string[]
81
     */
82
    //public $embeddedClasses = [];
83
84
    /**
85
     * READ-ONLY: The registered lifecycle callbacks for entities of this class.
86
     *
87
     * @var string[][]
88
     */
89
    public $lifecycleCallbacks = [];
90
91
    /**
92
     * READ-ONLY: The registered entity listeners.
93
     *
94
     * @var mixed[][]
95
     */
96
    public $entityListeners = [];
97
98
    /**
99
     * READ-ONLY: The field names of all fields that are part of the identifier/primary key
100
     * of the mapped entity class.
101
     *
102
     * @var string[]
103
     */
104
    public $identifier = [];
105
106
    /**
107
     * READ-ONLY: The inheritance mapping type used by the class.
108
     *
109
     * @var string
110
     */
111
    public $inheritanceType = InheritanceType::NONE;
112
113
    /**
114
     * READ-ONLY: The policy used for change-tracking on entities of this class.
115
     *
116
     * @var string
117
     */
118
    public $changeTrackingPolicy = ChangeTrackingPolicy::DEFERRED_IMPLICIT;
119
120
    /**
121
     * READ-ONLY: The discriminator value of this class.
122
     *
123
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
124
     * where a discriminator column is used.</b>
125
     *
126
     * @see discriminatorColumn
127
     *
128
     * @var mixed
129
     */
130
    public $discriminatorValue;
131
132
    /**
133
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
134
     *
135
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
136
     * where a discriminator column is used.</b>
137
     *
138
     * @see discriminatorColumn
139
     *
140
     * @var string[]
141
     */
142
    public $discriminatorMap = [];
143
144
    /**
145
     * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
146
     * inheritance mappings.
147
     *
148
     * @var DiscriminatorColumnMetadata
149
     */
150
    public $discriminatorColumn;
151
152
    /**
153
     * READ-ONLY: The primary table metadata.
154
     *
155
     * @var TableMetadata
156
     */
157
    public $table;
158
159
    /**
160
     * READ-ONLY: An array of field names. Used to look up field names from column names.
161
     * Keys are column names and values are field names.
162
     *
163
     * @var string[]
164
     */
165
    public $fieldNames = [];
166
167
    /**
168
     * READ-ONLY: The field which is used for versioning in optimistic locking (if any).
169
     *
170
     * @var FieldMetadata|null
171
     */
172
    public $versionProperty;
173
174
    /**
175
     * NamingStrategy determining the default column and table names.
176
     *
177
     * @var NamingStrategy
178
     */
179
    protected $namingStrategy;
180
181
    /**
182
     * Value generation plan is responsible for generating values for auto-generated fields.
183
     *
184
     * @var ValueGenerationPlan
185
     */
186
    protected $valueGenerationPlan;
187
188
    /**
189
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
190
     * metadata of the class with the given name.
191
     *
192
     * @param string             $entityName The name of the entity class.
193
     * @param ClassMetadata|null $parent     Optional parent class metadata.
194
     */
195 472
    public function __construct(
196
        string $entityName,
197
        ?ComponentMetadata $parent,
198
        ClassMetadataBuildingContext $metadataBuildingContext
199
    ) {
200 472
        parent::__construct($entityName, $metadataBuildingContext);
201
202 472
        $this->namingStrategy = $metadataBuildingContext->getNamingStrategy();
203
204 472
        if ($parent) {
205 98
            $this->setParent($parent);
206
        }
207 472
    }
208
209
    /**
210
     * {@inheritdoc}
211
     *
212
     * @throws MappingException
213
     */
214 98
    public function setParent(ComponentMetadata $parent) : void
215
    {
216 98
        parent::setParent($parent);
217
218 98
        foreach ($parent->getDeclaredPropertiesIterator() as $fieldName => $property) {
219 95
            $this->addInheritedProperty($property);
220
        }
221
222
        // @todo guilhermeblanco Assume to be a ClassMetadata temporarily until ClassMetadata split is complete.
223
        /** @var ClassMetadata $parent */
224 98
        $this->setInheritanceType($parent->inheritanceType);
0 ignored issues
show
$parent->inheritanceType of type string is incompatible with the type integer expected by parameter $type of Doctrine\ORM\Mapping\Cla...a::setInheritanceType(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

224
        $this->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
225 98
        $this->setIdentifier($parent->identifier);
226 98
        $this->setLifecycleCallbacks($parent->lifecycleCallbacks);
227 98
        $this->setChangeTrackingPolicy($parent->changeTrackingPolicy);
228
229 98
        if ($parent->discriminatorColumn) {
230 70
            $this->setDiscriminatorColumn($parent->discriminatorColumn);
231 70
            $this->setDiscriminatorMap($parent->discriminatorMap);
232
        }
233
234 98
        if ($parent->isMappedSuperclass) {
235 27
            $this->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
236
        }
237 98
    }
238
239
    public function setClassName(string $className)
240
    {
241
        $this->className = $className;
242
    }
243
244
    public function getColumnsIterator() : ArrayIterator
245
    {
246
        $iterator = parent::getColumnsIterator();
247
248
        if ($this->discriminatorColumn) {
249
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), $this->discriminatorColumn);
250
        }
251
252
        return $iterator;
253
    }
254
255 11
    public function getAncestorsIterator() : ArrayIterator
256
    {
257 11
        $ancestors = new ArrayIterator();
258 11
        $parent    = $this;
259
260 11
        while (($parent = $parent->parent) !== null) {
261 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
262 1
                continue;
263
            }
264
265 7
            $ancestors->append($parent);
266
        }
267
268 11
        return $ancestors;
269
    }
270
271 1260
    public function getRootClassName() : string
272
    {
273 1260
        return $this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass
274 402
            ? $this->parent->getRootClassName()
275 1260
            : $this->className;
276
    }
277
278
    /**
279
     * Handles metadata cloning nicely.
280
     */
281 13
    public function __clone()
282
    {
283 13
        if ($this->cache) {
284 12
            $this->cache = clone $this->cache;
285
        }
286
287 13
        foreach ($this->declaredProperties as $name => $property) {
288 13
            $this->declaredProperties[$name] = clone $property;
289
        }
290 13
    }
291
292
    /**
293
     * Creates a string representation of this instance.
294
     *
295
     * @return string The string representation of this instance.
296
     *
297
     * @todo Construct meaningful string representation.
298
     */
299
    public function __toString()
300
    {
301
        return self::class . '@' . spl_object_id($this);
302
    }
303
304
    /**
305
     * Determines which fields get serialized.
306
     *
307
     * It is only serialized what is necessary for best unserialization performance.
308
     * That means any metadata properties that are not set or empty or simply have
309
     * their default value are NOT serialized.
310
     *
311
     * Parts that are also NOT serialized because they can not be properly unserialized:
312
     * - reflectionClass
313
     *
314
     * @return string[] The names of all the fields that should be serialized.
315
     */
316 5
    public function __sleep()
317
    {
318 5
        $serialized = [];
319
320
        // This metadata is always serialized/cached.
321 5
        $serialized = array_merge($serialized, [
322 5
            'declaredProperties',
323
            'fieldNames',
324
            //'embeddedClasses',
325
            'identifier',
326
            'className',
327
            'parent',
328
            'table',
329
            'valueGenerationPlan',
330
        ]);
331
332
        // The rest of the metadata is only serialized if necessary.
333 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
334
            $serialized[] = 'changeTrackingPolicy';
335
        }
336
337 5
        if ($this->customRepositoryClassName) {
338 1
            $serialized[] = 'customRepositoryClassName';
339
        }
340
341 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
342 1
            $serialized[] = 'inheritanceType';
343 1
            $serialized[] = 'discriminatorColumn';
344 1
            $serialized[] = 'discriminatorValue';
345 1
            $serialized[] = 'discriminatorMap';
346 1
            $serialized[] = 'subClasses';
347
        }
348
349 5
        if ($this->isMappedSuperclass) {
350
            $serialized[] = 'isMappedSuperclass';
351
        }
352
353 5
        if ($this->isEmbeddedClass) {
354
            $serialized[] = 'isEmbeddedClass';
355
        }
356
357 5
        if ($this->isVersioned()) {
358
            $serialized[] = 'versionProperty';
359
        }
360
361 5
        if ($this->lifecycleCallbacks) {
362
            $serialized[] = 'lifecycleCallbacks';
363
        }
364
365 5
        if ($this->entityListeners) {
366 1
            $serialized[] = 'entityListeners';
367
        }
368
369 5
        if ($this->cache) {
370
            $serialized[] = 'cache';
371
        }
372
373 5
        if ($this->readOnly) {
374 1
            $serialized[] = 'readOnly';
375
        }
376
377 5
        return $serialized;
378
    }
379
380
    /**
381
     * Restores some state that can not be serialized/unserialized.
382
     */
383 1634
    public function wakeupReflection(ReflectionService $reflectionService) : void
384
    {
385
        // Restore ReflectionClass and properties
386 1634
        $this->reflectionClass = $reflectionService->getClass($this->className);
387
388 1634
        if (! $this->reflectionClass) {
389
            return;
390
        }
391
392 1634
        $this->className = $this->reflectionClass->getName();
393
394 1634
        foreach ($this->declaredProperties as $property) {
395
            /** @var Property $property */
396 1633
            $property->wakeupReflection($reflectionService);
397
        }
398 1634
    }
399
400
    /**
401
     * Validates Identifier.
402
     *
403
     * @throws MappingException
404
     */
405 370
    public function validateIdentifier() : void
406
    {
407 370
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
408 27
            return;
409
        }
410
411
        // Verify & complete identifier mapping
412 370
        if (! $this->identifier) {
413 4
            throw MappingException::identifierRequired($this->className);
414
        }
415
416
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, static function (Property $property) : bool {
417 366
            return $property instanceof FieldMetadata
418 366
                && $property->isPrimaryKey()
419 366
                && $property->hasValueGenerator();
420 366
        });
421
422 366
        if ($explicitlyGeneratedProperties && $this->isIdentifierComposite()) {
423
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
424
        }
425 366
    }
426
427
    /**
428
     * Validates association targets actually exist.
429
     *
430
     * @throws MappingException
431
     */
432 369
    public function validateAssociations() : void
433
    {
434 369
        array_map(
435
            function (Property $property) {
436 369
                if (! ($property instanceof AssociationMetadata)) {
437 366
                    return;
438
                }
439
440 251
                $targetEntity = $property->getTargetEntity();
441
442 251
                if (! class_exists($targetEntity)) {
443 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
444
                }
445 369
            },
446 369
            $this->declaredProperties
447
        );
448 368
    }
449
450
    /**
451
     * Validates lifecycle callbacks.
452
     *
453
     * @throws MappingException
454
     */
455 369
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
456
    {
457 369
        foreach ($this->lifecycleCallbacks as $callbacks) {
458
            /** @var array $callbacks */
459 10
            foreach ($callbacks as $callbackFuncName) {
460 10
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
461 1
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
462
                }
463
            }
464
        }
465 368
    }
466
467
    /**
468
     * Sets the change tracking policy used by this class.
469
     */
470 104
    public function setChangeTrackingPolicy(string $policy) : void
471
    {
472 104
        $this->changeTrackingPolicy = $policy;
473 104
    }
474
475
    /**
476
     * Checks whether a field is part of the identifier/primary key field(s).
477
     *
478
     * @param string $fieldName The field name.
479
     *
480
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
481
     */
482 1027
    public function isIdentifier(string $fieldName) : bool
483
    {
484 1027
        if (! $this->identifier) {
485 1
            return false;
486
        }
487
488 1026
        if (! $this->isIdentifierComposite()) {
489 1022
            return $fieldName === $this->identifier[0];
490
        }
491
492 93
        return in_array($fieldName, $this->identifier, true);
493
    }
494
495 1214
    public function isIdentifierComposite() : bool
496
    {
497 1214
        return isset($this->identifier[1]);
498
    }
499
500
    /**
501
     * Validates & completes the basic mapping information for field mapping.
502
     *
503
     * @throws MappingException If something is wrong with the mapping.
504
     */
505 414
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
506
    {
507 414
        $fieldName  = $property->getName();
508 414
        $columnName = $property->getColumnName();
509
510 414
        if (empty($columnName)) {
511 350
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
512
513 350
            $property->setColumnName($columnName);
514
        }
515
516 414
        if (! $this->isMappedSuperclass) {
517 407
            $property->setTableName($this->getTableName());
518
        }
519
520
        // Check for already declared column
521 414
        if (isset($this->fieldNames[$columnName]) ||
522 414
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
523 2
            throw MappingException::duplicateColumnName($this->className, $columnName);
524
        }
525
526
        // Complete id mapping
527 413
        if ($property->isPrimaryKey()) {
528 398
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
529
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
530
            }
531
532 398
            if ($property->getType()->canRequireSQLConversion()) {
533
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($this->className, $property);
534
            }
535
536 398
            if (! in_array($fieldName, $this->identifier, true)) {
537 398
                $this->identifier[] = $fieldName;
538
            }
539
        }
540
541 413
        $this->fieldNames[$columnName] = $fieldName;
542 413
    }
543
544
    /**
545
     * Validates & completes the basic mapping information for field mapping.
546
     *
547
     * @throws MappingException If something is wrong with the mapping.
548
     */
549 21
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
550
    {
551 21
        $this->versionProperty = $property;
552
553 21
        $options = $property->getOptions();
554
555 21
        if (isset($options['default'])) {
556
            return;
557
        }
558
559 21
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'], true)) {
560 19
            $property->setOptions(array_merge($options, ['default' => 1]));
561
562 19
            return;
563
        }
564
565 3
        if (in_array($property->getTypeName(), ['datetime', 'datetime_immutable', 'datetimetz', 'datetimetz_immutable'], true)) {
566 2
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
567
568 2
            return;
569
        }
570
571 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
572
    }
573
574
    /**
575
     * Validates & completes the basic mapping information that is common to all
576
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
577
     *
578
     * @throws MappingException If something is wrong with the mapping.
579
     * @throws CacheException   If entity is not cacheable.
580
     */
581 291
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
582
    {
583 291
        $fieldName    = $property->getName();
584 291
        $targetEntity = $property->getTargetEntity();
585
586 291
        if (! $targetEntity) {
587
            throw MappingException::missingTargetEntity($fieldName);
588
        }
589
590 291
        $property->setSourceEntity($this->className);
591 291
        $property->setTargetEntity($targetEntity);
592
593
        // Complete id mapping
594 291
        if ($property->isPrimaryKey()) {
595 47
            if ($property->isOrphanRemoval()) {
596 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
597
            }
598
599 46
            if (! in_array($property->getName(), $this->identifier, true)) {
600 46
                if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
601
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
602
                        $property->getTargetEntity(),
603
                        $this->className,
604
                        $fieldName
605
                    );
606
                }
607
608 46
                $this->identifier[] = $property->getName();
609
            }
610
611 46
            if ($this->cache && ! $property->getCache()) {
612 2
                throw NonCacheableEntityAssociation::fromEntityAndField($this->className, $fieldName);
613
            }
614
615 44
            if ($property instanceof ToManyAssociationMetadata) {
616 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
617
            }
618
        }
619
620
        // Cascades
621 287
        $cascadeTypes = ['remove', 'persist', 'refresh'];
622 287
        $cascades     = array_map('strtolower', $property->getCascade());
623
624 287
        if (in_array('all', $cascades, true)) {
625 6
            $cascades = $cascadeTypes;
626
        }
627
628 287
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
629 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
630
631 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
632
        }
633
634 286
        $property->setCascade($cascades);
635 286
    }
636
637
    /**
638
     * Validates & completes a to-one association mapping.
639
     *
640
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
641
     *
642
     * @throws RuntimeException
643
     * @throws MappingException
644
     */
645 249
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
646
    {
647 249
        $fieldName = $property->getName();
648
649 249
        if ($property->isOwningSide()) {
650 245
            if (empty($property->getJoinColumns())) {
651
                // Apply default join column
652 86
                $property->addJoinColumn(new JoinColumnMetadata());
653
            }
654
655 245
            $uniqueConstraintColumns = [];
656
657 245
            foreach ($property->getJoinColumns() as $joinColumn) {
658
                /** @var JoinColumnMetadata $joinColumn */
659 245
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
660 112
                    if (count($property->getJoinColumns()) === 1) {
661 110
                        if (! $property->isPrimaryKey()) {
662 110
                            $joinColumn->setUnique(true);
663
                        }
664
                    } else {
665 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
666
                    }
667
                }
668
669 245
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
670
671 245
                if (! $joinColumn->getColumnName()) {
672 100
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
673
                }
674
675 245
                if (! $joinColumn->getReferencedColumnName()) {
676 86
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
677
                }
678
679 245
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
680
            }
681
682 245
            if ($uniqueConstraintColumns) {
683 2
                if (! $this->table) {
684
                    throw new RuntimeException(
685
                        'ClassMetadata::setTable() has to be called before defining a one to one relationship.'
686
                    );
687
                }
688
689 2
                $this->table->addUniqueConstraint(
690
                    [
691 2
                        'name'    => sprintf('%s_uniq', $fieldName),
692 2
                        'columns' => $uniqueConstraintColumns,
693
                        'options' => [],
694
                        'flags'   => [],
695
                    ]
696
                );
697
            }
698
        }
699
700 249
        if ($property->isOrphanRemoval()) {
701 7
            $cascades = $property->getCascade();
702
703 7
            if (! in_array('remove', $cascades, true)) {
704 6
                $cascades[] = 'remove';
705
706 6
                $property->setCascade($cascades);
707
            }
708
709
            // @todo guilhermeblanco where is this used?
710
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
711
            //$property->setUnique(false);
712
        }
713
714 249
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
715 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
716
        }
717 248
    }
718
719
    /**
720
     * Validates & completes a to-many association mapping.
721
     *
722
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
723
     *
724
     * @throws MappingException
725
     */
726 175
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
727
    {
728
        // Do nothing
729 175
    }
730
731
    /**
732
     * Validates & completes a one-to-one association mapping.
733
     *
734
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
735
     */
736 129
    protected function validateAndCompleteOneToOneMapping(OneToOneAssociationMetadata $property)
737
    {
738
        // Do nothing
739 129
    }
740
741
    /**
742
     * Validates & completes a many-to-one association mapping.
743
     *
744
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
745
     *
746
     * @throws MappingException
747
     */
748 149
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
749
    {
750
        // A many-to-one mapping is essentially a one-one backreference
751 149
        if ($property->isOrphanRemoval()) {
752
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
753
        }
754 149
    }
755
756
    /**
757
     * Validates & completes a one-to-many association mapping.
758
     *
759
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
760
     *
761
     * @throws MappingException
762
     */
763 118
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
764
    {
765
        // OneToMany MUST have mappedBy
766 118
        if (! $property->getMappedBy()) {
767
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
768
        }
769
770 118
        if ($property->isOrphanRemoval()) {
771 19
            $cascades = $property->getCascade();
772
773 19
            if (! in_array('remove', $cascades, true)) {
774 16
                $cascades[] = 'remove';
775
776 16
                $property->setCascade($cascades);
777
            }
778
        }
779 118
    }
780
781
    /**
782
     * Validates & completes a many-to-many association mapping.
783
     *
784
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
785
     *
786
     * @throws MappingException
787
     */
788 110
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
789
    {
790 110
        if ($property->isOwningSide()) {
791
            // owning side MUST have a join table
792 97
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
793
794 97
            $property->setJoinTable($joinTable);
795
796 97
            if (! $joinTable->getName()) {
797 18
                $joinTableName = $this->namingStrategy->joinTableName(
798 18
                    $property->getSourceEntity(),
799 18
                    $property->getTargetEntity(),
800 18
                    $property->getName()
801
                );
802
803 18
                $joinTable->setName($joinTableName);
804
            }
805
806 97
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() === $property->getTargetEntity() && ! $joinTable->hasColumns();
807
808 97
            if (! $joinTable->getJoinColumns()) {
809 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
810 16
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
811 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
812 16
                $joinColumn           = new JoinColumnMetadata();
813
814 16
                $joinColumn->setColumnName($columnName);
815 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
816 16
                $joinColumn->setOnDelete('CASCADE');
817
818 16
                $joinTable->addJoinColumn($joinColumn);
819
            }
820
821 97
            if (! $joinTable->getInverseJoinColumns()) {
822 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
823 16
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
824 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
825 16
                $joinColumn           = new JoinColumnMetadata();
826
827 16
                $joinColumn->setColumnName($columnName);
828 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
829 16
                $joinColumn->setOnDelete('CASCADE');
830
831 16
                $joinTable->addInverseJoinColumn($joinColumn);
832
            }
833
834 97
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
835
                /** @var JoinColumnMetadata $joinColumn */
836 97
                if (! $joinColumn->getReferencedColumnName()) {
837 2
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
838
                }
839
840 97
                $referencedColumnName = $joinColumn->getReferencedColumnName();
841
842 97
                if (! $joinColumn->getColumnName()) {
843 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
844 2
                        $property->getSourceEntity(),
845 2
                        $referencedColumnName
846
                    );
847
848 2
                    $joinColumn->setColumnName($columnName);
849
                }
850
            }
851
852 97
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
853
                /** @var JoinColumnMetadata $inverseJoinColumn */
854 97
                if (! $inverseJoinColumn->getReferencedColumnName()) {
855 2
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
856
                }
857
858 97
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
859
860 97
                if (! $inverseJoinColumn->getColumnName()) {
861 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
862 2
                        $property->getTargetEntity(),
863 2
                        $referencedColumnName
864
                    );
865
866 2
                    $inverseJoinColumn->setColumnName($columnName);
867
                }
868
            }
869
        }
870 110
    }
871
872
    /**
873
     * {@inheritDoc}
874
     */
875 401
    public function getIdentifierFieldNames()
876
    {
877 401
        return $this->identifier;
878
    }
879
880
    /**
881
     * Gets the name of the single id field. Note that this only works on
882
     * entity classes that have a single-field pk.
883
     *
884
     * @return string
885
     *
886
     * @throws MappingException If the class has a composite primary key.
887
     */
888 151
    public function getSingleIdentifierFieldName()
889
    {
890 151
        if ($this->isIdentifierComposite()) {
891 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
892
        }
893
894 150
        if (! isset($this->identifier[0])) {
895 1
            throw MappingException::noIdDefined($this->className);
896
        }
897
898 149
        return $this->identifier[0];
899
    }
900
901
    /**
902
     * INTERNAL:
903
     * Sets the mapped identifier/primary key fields of this class.
904
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
905
     *
906
     * @param mixed[] $identifier
907
     */
908 100
    public function setIdentifier(array $identifier)
909
    {
910 100
        $this->identifier = $identifier;
911 100
    }
912
913
    /**
914
     * {@inheritDoc}
915
     */
916 1053
    public function getIdentifier()
917
    {
918 1053
        return $this->identifier;
919
    }
920
921
    /**
922
     * {@inheritDoc}
923
     */
924 189
    public function hasField($fieldName)
925
    {
926 189
        return isset($this->declaredProperties[$fieldName])
927 189
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
928
    }
929
930
    /**
931
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
932
     *
933
     * @return ColumnMetadata[]
934
     */
935 441
    public function getIdentifierColumns(EntityManagerInterface $em) : array
936
    {
937 441
        $columns = [];
938
939 441
        foreach ($this->identifier as $idProperty) {
940 441
            $property = $this->getProperty($idProperty);
941
942 441
            if ($property instanceof FieldMetadata) {
943 435
                $columns[$property->getColumnName()] = $property;
944
945 435
                continue;
946
            }
947
948
            /** @var AssociationMetadata $property */
949
950
            // Association defined as Id field
951 25
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
952
953 25
            if (! $property->isOwningSide()) {
954
                $property    = $targetClass->getProperty($property->getMappedBy());
955
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
956
            }
957
958 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
959
                ? $property->getJoinTable()->getInverseJoinColumns()
960 25
                : $property->getJoinColumns();
961
962 25
            foreach ($joinColumns as $joinColumn) {
963
                /** @var JoinColumnMetadata $joinColumn */
964 25
                $columnName           = $joinColumn->getColumnName();
965 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
966
967 25
                if (! $joinColumn->getType()) {
968 13
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
0 ignored issues
show
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

968
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, /** @scrutinizer ignore-type */ $targetClass, $em));
Loading history...
969
                }
970
971 25
                $columns[$columnName] = $joinColumn;
972
            }
973
        }
974
975 441
        return $columns;
976
    }
977
978
    /**
979
     * Gets the name of the primary table.
980
     */
981 1597
    public function getTableName() : ?string
982
    {
983 1597
        return $this->table->getName();
984
    }
985
986
    /**
987
     * Gets primary table's schema name.
988
     */
989 14
    public function getSchemaName() : ?string
990
    {
991 14
        return $this->table->getSchema();
992
    }
993
994
    /**
995
     * Gets the table name to use for temporary identifier tables of this class.
996
     */
997 7
    public function getTemporaryIdTableName() : string
998
    {
999 7
        $schema = $this->getSchemaName() === null
1000 6
            ? ''
1001 7
            : $this->getSchemaName() . '_';
1002
1003
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
1004 7
        return $schema . $this->getTableName() . '_id_tmp';
1005
    }
1006
1007
    /**
1008
     * Sets the mapped subclasses of this class.
1009
     *
1010
     * @param string[] $subclasses The names of all mapped subclasses.
1011
     *
1012
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
1013
     */
1014 4
    public function setSubclasses(array $subclasses) : void
1015
    {
1016 4
        foreach ($subclasses as $subclass) {
1017 3
            $this->subClasses[] = $subclass;
1018
        }
1019 4
    }
1020
1021
    /**
1022
     * @return string[]
1023
     */
1024 1080
    public function getSubClasses() : array
1025
    {
1026 1080
        return $this->subClasses;
1027
    }
1028
1029
    /**
1030
     * Sets the inheritance type used by the class and its subclasses.
1031
     *
1032
     * @param int $type
1033
     *
1034
     * @throws MappingException
1035
     */
1036 120
    public function setInheritanceType($type) : void
1037
    {
1038 120
        if (! $this->isInheritanceType($type)) {
1039
            throw MappingException::invalidInheritanceType($this->className, $type);
1040
        }
1041
1042 120
        $this->inheritanceType = $type;
1043 120
    }
1044
1045
    /**
1046
     * Sets the override property mapping for an entity relationship.
1047
     *
1048
     * @throws RuntimeException
1049
     * @throws MappingException
1050
     * @throws CacheException
1051
     */
1052 12
    public function setPropertyOverride(Property $property) : void
1053
    {
1054 12
        $fieldName = $property->getName();
1055
1056 12
        if (! isset($this->declaredProperties[$fieldName])) {
1057 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1058
        }
1059
1060 10
        $originalProperty          = $this->getProperty($fieldName);
1061 10
        $originalPropertyClassName = get_class($originalProperty);
1062
1063
        // If moving from transient to persistent, assume it's a new property
1064 10
        if ($originalPropertyClassName === TransientMetadata::class) {
1065 1
            unset($this->declaredProperties[$fieldName]);
1066
1067 1
            $this->addProperty($property);
1068
1069 1
            return;
1070
        }
1071
1072
        // Do not allow to change property type
1073 9
        if ($originalPropertyClassName !== get_class($property)) {
1074
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1075
        }
1076
1077
        // Do not allow to change version property
1078 9
        if ($originalProperty instanceof VersionFieldMetadata) {
1079
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1080
        }
1081
1082 9
        unset($this->declaredProperties[$fieldName]);
1083
1084 9
        if ($property instanceof FieldMetadata) {
1085
            // Unset defined fieldName prior to override
1086 5
            unset($this->fieldNames[$originalProperty->getColumnName()]);
1087
1088
            // Revert what should not be allowed to change
1089 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1090 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1091 9
        } elseif ($property instanceof AssociationMetadata) {
1092
            // Unset all defined fieldNames prior to override
1093 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1094 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1095 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1096
                }
1097
            }
1098
1099
            // Override what it should be allowed to change
1100 9
            if ($property->getInversedBy()) {
1101 2
                $originalProperty->setInversedBy($property->getInversedBy());
1102
            }
1103
1104 9
            if ($property->getFetchMode() !== $originalProperty->getFetchMode()) {
1105 2
                $originalProperty->setFetchMode($property->getFetchMode());
1106
            }
1107
1108 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) {
1109 5
                $originalProperty->setJoinColumns($property->getJoinColumns());
1110 8
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) {
1111 4
                $originalProperty->setJoinTable($property->getJoinTable());
1112
            }
1113
1114 9
            $property = $originalProperty;
1115
        }
1116
1117 9
        $this->addProperty($property);
1118 9
    }
1119
1120
    /**
1121
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1122
     *
1123
     * @return bool
1124
     */
1125 336
    public function isRootEntity()
1126
    {
1127 336
        return $this->className === $this->getRootClassName();
1128
    }
1129
1130
    /**
1131
     * Checks whether a mapped field is inherited from a superclass.
1132
     *
1133
     * @param string $fieldName
1134
     *
1135
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1136
     */
1137 622
    public function isInheritedProperty($fieldName)
1138
    {
1139 622
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1140
1141 622
        return $declaringClass->className !== $this->className;
1142
    }
1143
1144
    /**
1145
     * {@inheritdoc}
1146
     */
1147 450
    public function setTable(TableMetadata $table) : void
1148
    {
1149 450
        $this->table = $table;
1150
1151 450
        if (empty($table->getName())) {
1152
            $table->setName($this->namingStrategy->classToTableName($this->className));
1153
        }
1154 450
    }
1155
1156
    /**
1157
     * Checks whether the given type identifies an inheritance type.
1158
     *
1159
     * @param int $type
1160
     *
1161
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1162
     */
1163 120
    private function isInheritanceType($type)
1164
    {
1165 120
        return $type === InheritanceType::NONE
1166 93
            || $type === InheritanceType::SINGLE_TABLE
1167 51
            || $type === InheritanceType::JOINED
1168 120
            || $type === InheritanceType::TABLE_PER_CLASS;
1169
    }
1170
1171 916
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1172
    {
1173 916
        foreach ($this->declaredProperties as $property) {
1174 916
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1175 916
                return $property;
1176
            }
1177
        }
1178
1179
        return null;
1180
    }
1181
1182
    /**
1183
     * Add a property mapping.
1184
     *
1185
     * @throws RuntimeException
1186
     * @throws MappingException
1187
     * @throws CacheException
1188
     */
1189 440
    public function addProperty(Property $property)
1190
    {
1191 440
        $fieldName = $property->getName();
1192
1193
        // Check for empty field name
1194 440
        if (empty($fieldName)) {
1195 1
            throw MappingException::missingFieldName($this->className);
1196
        }
1197
1198 439
        $property->setDeclaringClass($this);
1199
1200
        switch (true) {
1201 439
            case $property instanceof VersionFieldMetadata:
1202 21
                $this->validateAndCompleteFieldMapping($property);
1203 21
                $this->validateAndCompleteVersionFieldMapping($property);
1204 20
                break;
1205
1206 438
            case $property instanceof FieldMetadata:
1207 413
                $this->validateAndCompleteFieldMapping($property);
1208 412
                break;
1209
1210 294
            case $property instanceof OneToOneAssociationMetadata:
1211 131
                $this->validateAndCompleteAssociationMapping($property);
1212 130
                $this->validateAndCompleteToOneAssociationMetadata($property);
1213 129
                $this->validateAndCompleteOneToOneMapping($property);
1214 129
                break;
1215
1216 230
            case $property instanceof OneToManyAssociationMetadata:
1217 118
                $this->validateAndCompleteAssociationMapping($property);
1218 118
                $this->validateAndCompleteToManyAssociationMetadata($property);
1219 118
                $this->validateAndCompleteOneToManyMapping($property);
1220 118
                break;
1221
1222 225
            case $property instanceof ManyToOneAssociationMetadata:
1223 152
                $this->validateAndCompleteAssociationMapping($property);
1224 149
                $this->validateAndCompleteToOneAssociationMetadata($property);
1225 149
                $this->validateAndCompleteManyToOneMapping($property);
1226 149
                break;
1227
1228 127
            case $property instanceof ManyToManyAssociationMetadata:
1229 111
                $this->validateAndCompleteAssociationMapping($property);
1230 110
                $this->validateAndCompleteToManyAssociationMetadata($property);
1231 110
                $this->validateAndCompleteManyToManyMapping($property);
1232 110
                break;
1233
1234
            default:
1235
                // Transient properties are ignored on purpose here! =)
1236 30
                break;
1237
        }
1238
1239 431
        $this->addDeclaredProperty($property);
1240 431
    }
1241
1242
    /**
1243
     * INTERNAL:
1244
     * Adds a property mapping without completing/validating it.
1245
     * This is mainly used to add inherited property mappings to derived classes.
1246
     */
1247 96
    public function addInheritedProperty(Property $property)
1248
    {
1249 96
        $inheritedProperty = clone $property;
1250 96
        $declaringClass    = $property->getDeclaringClass();
1251
1252 96
        if ($inheritedProperty instanceof FieldMetadata) {
1253 95
            if (! $declaringClass->isMappedSuperclass) {
1254 73
                $inheritedProperty->setTableName($property->getTableName());
1255
            }
1256
1257 95
            $this->fieldNames[$property->getColumnName()] = $property->getName();
1258 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
1259 42
            if ($declaringClass->isMappedSuperclass) {
1260 10
                $inheritedProperty->setSourceEntity($this->className);
1261
            }
1262
1263
            // Need to add inherited fieldNames
1264 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1265 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1266
                    /** @var JoinColumnMetadata $joinColumn */
1267 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1268
                }
1269
            }
1270
        }
1271
1272 96
        if (isset($this->declaredProperties[$property->getName()])) {
1273 1
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
1274
        }
1275
1276 96
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1277
1278 96
        if ($inheritedProperty instanceof VersionFieldMetadata) {
1279 4
            $this->versionProperty = $inheritedProperty;
1280
        }
1281 96
    }
1282
1283
    /**
1284
     * Registers a custom repository class for the entity class.
1285
     *
1286
     * @param string|null $repositoryClassName The class name of the custom mapper.
1287
     */
1288 30
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1289
    {
1290 30
        $this->customRepositoryClassName = $repositoryClassName;
1291 30
    }
1292
1293 163
    public function getCustomRepositoryClassName() : ?string
1294
    {
1295 163
        return $this->customRepositoryClassName;
1296
    }
1297
1298
    /**
1299
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1300
     *
1301
     * @param string $lifecycleEvent
1302
     *
1303
     * @return bool
1304
     */
1305
    public function hasLifecycleCallbacks($lifecycleEvent)
1306
    {
1307
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1308
    }
1309
1310
    /**
1311
     * Gets the registered lifecycle callbacks for an event.
1312
     *
1313
     * @param string $event
1314
     *
1315
     * @return string[]
1316
     */
1317
    public function getLifecycleCallbacks($event)
1318
    {
1319
        return $this->lifecycleCallbacks[$event] ?? [];
1320
    }
1321
1322
    /**
1323
     * Adds a lifecycle callback for entities of this class.
1324
     *
1325
     * @param string $callback
1326
     * @param string $event
1327
     */
1328 16
    public function addLifecycleCallback($callback, $event)
1329
    {
1330 16
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) {
1331 3
            return;
1332
        }
1333
1334 16
        $this->lifecycleCallbacks[$event][] = $callback;
1335 16
    }
1336
1337
    /**
1338
     * Sets the lifecycle callbacks for entities of this class.
1339
     * Any previously registered callbacks are overwritten.
1340
     *
1341
     * @param string[][] $callbacks
1342
     */
1343 98
    public function setLifecycleCallbacks(array $callbacks) : void
1344
    {
1345 98
        $this->lifecycleCallbacks = $callbacks;
1346 98
    }
1347
1348
    /**
1349
     * Adds a entity listener for entities of this class.
1350
     *
1351
     * @param string $eventName The entity lifecycle event.
1352
     * @param string $class     The listener class.
1353
     * @param string $method    The listener callback method.
1354
     *
1355
     * @throws MappingException
1356
     */
1357 17
    public function addEntityListener(string $eventName, string $class, string $method) : void
1358
    {
1359
        $listener = [
1360 17
            'class'  => $class,
1361 17
            'method' => $method,
1362
        ];
1363
1364 17
        if (! class_exists($class)) {
1365 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1366
        }
1367
1368 16
        if (! method_exists($class, $method)) {
1369 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1370
        }
1371
1372 15
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
1373 1
            throw MappingException::duplicateEntityListener($class, $method, $this->className);
1374
        }
1375
1376 15
        $this->entityListeners[$eventName][] = $listener;
1377 15
    }
1378
1379
    /**
1380
     * Sets the discriminator column definition.
1381
     *
1382
     * @see getDiscriminatorColumn()
1383
     *
1384
     * @throws MappingException
1385
     */
1386 95
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1387
    {
1388 95
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1389 1
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1390
        }
1391
1392 94
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1393
1394 94
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1395
1396 94
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1397
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1398
        }
1399
1400 94
        $this->discriminatorColumn = $discriminatorColumn;
1401 94
    }
1402
1403
    /**
1404
     * Sets the discriminator values used by this class.
1405
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1406
     *
1407
     * @param string[] $map
1408
     *
1409
     * @throws MappingException
1410
     */
1411 89
    public function setDiscriminatorMap(array $map) : void
1412
    {
1413 89
        foreach ($map as $value => $className) {
1414 89
            $this->addDiscriminatorMapClass($value, $className);
1415
        }
1416 89
    }
1417
1418
    /**
1419
     * Adds one entry of the discriminator map with a new class and corresponding name.
1420
     *
1421
     * @param string|int $name
1422
     *
1423
     * @throws MappingException
1424
     */
1425 89
    public function addDiscriminatorMapClass($name, string $className) : void
1426
    {
1427 89
        $this->discriminatorMap[$name] = $className;
1428
1429 89
        if ($this->className === $className) {
1430 75
            $this->discriminatorValue = $name;
1431
1432 75
            return;
1433
        }
1434
1435 88
        if (! (class_exists($className) || interface_exists($className))) {
1436
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1437
        }
1438
1439 88
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1440 83
            $this->subClasses[] = $className;
1441
        }
1442 88
    }
1443
1444 1031
    public function getValueGenerationPlan() : ValueGenerationPlan
1445
    {
1446 1031
        return $this->valueGenerationPlan;
1447
    }
1448
1449 370
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1450
    {
1451 370
        $this->valueGenerationPlan = $valueGenerationPlan;
1452 370
    }
1453
1454
    /**
1455
     * Marks this class as read only, no change tracking is applied to it.
1456
     */
1457 2
    public function asReadOnly() : void
1458
    {
1459 2
        $this->readOnly = true;
1460 2
    }
1461
1462
    /**
1463
     * Whether this class is read only or not.
1464
     */
1465 446
    public function isReadOnly() : bool
1466
    {
1467 446
        return $this->readOnly;
1468
    }
1469
1470 1092
    public function isVersioned() : bool
1471
    {
1472 1092
        return $this->versionProperty !== null;
1473
    }
1474
1475
    /**
1476
     * Map Embedded Class
1477
     *
1478
     * @param mixed[] $mapping
1479
     *
1480
     * @throws MappingException
1481
     */
1482
    public function mapEmbedded(array $mapping) : void
1483
    {
1484
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
1485
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1486
        }
1487
1488
        $this->embeddedClasses[$mapping['fieldName']] = [
1489
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1490
            'columnPrefix'   => $mapping['columnPrefix'],
1491
            'declaredField'  => $mapping['declaredField'] ?? null,
1492
            'originalField'  => $mapping['originalField'] ?? null,
1493
            'declaringClass' => $this,
1494
        ];*/
1495
    }
1496
1497
    /**
1498
     * Inline the embeddable class
1499
     *
1500
     * @param string $property
1501
     */
1502
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
1503
    {
1504
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1505
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1506
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1507
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1508
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1509
                ? $property . '.' . $fieldMapping['declaredField']
1510
                : $property;
1511
1512
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1513
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1514
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1515
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1516
                    $property,
1517
                    $fieldMapping['columnName'],
1518
                    $this->reflectionClass->getName(),
1519
                    $embeddable->reflectionClass->getName()
1520
                );
1521
            }
1522
1523
            $this->mapField($fieldMapping);
1524
        }*/
1525
    }
1526
}
1527