Passed
Push — master ( 89e39b...ec508a )
by Marco
11:02
created

lib/Doctrine/ORM/Mapping/ClassMetadata.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping;
6
7
use Doctrine\ORM\Cache\CacheException;
8
use Doctrine\ORM\EntityManagerInterface;
9
use Doctrine\ORM\Mapping\Factory\NamingStrategy;
10
use Doctrine\ORM\Reflection\ReflectionService;
11
use Doctrine\ORM\Sequencing\Planning\ValueGenerationPlan;
12
use Doctrine\ORM\Utility\PersisterHelper;
13
use function array_diff;
14
use function array_filter;
15
use function array_intersect;
16
use function array_map;
17
use function array_merge;
18
use function class_exists;
19
use function count;
20
use function explode;
21
use function get_class;
22
use function in_array;
23
use function interface_exists;
24
use function is_subclass_of;
25
use function method_exists;
26
use function spl_object_id;
27
use function sprintf;
28
use function strpos;
29
30
/**
31
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
32
 * of an entity and its associations.
33
 *
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
     * @var mixed
127
     *
128
     * @see discriminatorColumn
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
     * @var string[]
139
     *
140
     * @see discriminatorColumn
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
     */
194 459
    public function __construct(
195
        string $entityName,
196
        ClassMetadataBuildingContext $metadataBuildingContext
197
    ) {
198 459
        parent::__construct($entityName, $metadataBuildingContext);
199
200 459
        $this->namingStrategy = $metadataBuildingContext->getNamingStrategy();
201 459
    }
202
203 2
    public function setClassName(string $className)
204
    {
205 2
        $this->className = $className;
206 2
    }
207
208
    public function getColumnsIterator() : \ArrayIterator
209
    {
210
        $iterator = parent::getColumnsIterator();
211
212
        if ($this->discriminatorColumn) {
213
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), $this->discriminatorColumn);
214
        }
215
216
        return $iterator;
217
    }
218
219 11
    public function getAncestorsIterator() : \ArrayIterator
220
    {
221 11
        $ancestors = new \ArrayIterator();
222 11
        $parent    = $this;
223
224 11
        while (($parent = $parent->parent) !== null) {
225 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
226 1
                continue;
227
            }
228
229 7
            $ancestors->append($parent);
230
        }
231
232 11
        return $ancestors;
233
    }
234
235 1257
    public function getRootClassName() : string
236
    {
237 1257
        return ($this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass)
238 403
            ? $this->parent->getRootClassName()
239 1257
            : $this->className
240
        ;
241
    }
242
243
    /**
244
     * Handles metadata cloning nicely.
245
     */
246 13
    public function __clone()
247
    {
248 13
        if ($this->cache) {
249 12
            $this->cache = clone $this->cache;
250
        }
251
252 13
        foreach ($this->declaredProperties as $name => $property) {
253 13
            $this->declaredProperties[$name] = clone $property;
254
        }
255 13
    }
256
257
    /**
258
     * Creates a string representation of this instance.
259
     *
260
     * @return string The string representation of this instance.
261
     *
262
     * @todo Construct meaningful string representation.
263
     */
264
    public function __toString()
265
    {
266
        return __CLASS__ . '@' . spl_object_id($this);
267
    }
268
269
    /**
270
     * Determines which fields get serialized.
271
     *
272
     * It is only serialized what is necessary for best unserialization performance.
273
     * That means any metadata properties that are not set or empty or simply have
274
     * their default value are NOT serialized.
275
     *
276
     * Parts that are also NOT serialized because they can not be properly unserialized:
277
     * - reflectionClass
278
     *
279
     * @return string[] The names of all the fields that should be serialized.
280
     */
281 5
    public function __sleep()
282
    {
283 5
        $serialized = [];
284
285
        // This metadata is always serialized/cached.
286 5
        $serialized = array_merge($serialized, [
287 5
            'declaredProperties',
288
            'fieldNames',
289
            //'embeddedClasses',
290
            'identifier',
291
            'className',
292
            'parent',
293
            'table',
294
            'valueGenerationPlan',
295
        ]);
296
297
        // The rest of the metadata is only serialized if necessary.
298 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
299
            $serialized[] = 'changeTrackingPolicy';
300
        }
301
302 5
        if ($this->customRepositoryClassName) {
303 1
            $serialized[] = 'customRepositoryClassName';
304
        }
305
306 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
307 1
            $serialized[] = 'inheritanceType';
308 1
            $serialized[] = 'discriminatorColumn';
309 1
            $serialized[] = 'discriminatorValue';
310 1
            $serialized[] = 'discriminatorMap';
311 1
            $serialized[] = 'subClasses';
312
        }
313
314 5
        if ($this->isMappedSuperclass) {
315
            $serialized[] = 'isMappedSuperclass';
316
        }
317
318 5
        if ($this->isEmbeddedClass) {
319
            $serialized[] = 'isEmbeddedClass';
320
        }
321
322 5
        if ($this->isVersioned()) {
323
            $serialized[] = 'versionProperty';
324
        }
325
326 5
        if ($this->lifecycleCallbacks) {
327
            $serialized[] = 'lifecycleCallbacks';
328
        }
329
330 5
        if ($this->entityListeners) {
331 1
            $serialized[] = 'entityListeners';
332
        }
333
334 5
        if ($this->cache) {
335
            $serialized[] = 'cache';
336
        }
337
338 5
        if ($this->readOnly) {
339 1
            $serialized[] = 'readOnly';
340
        }
341
342 5
        return $serialized;
343
    }
344
345
    /**
346
     * Restores some state that can not be serialized/unserialized.
347
     */
348 1625
    public function wakeupReflection(ReflectionService $reflectionService) : void
349
    {
350
        // Restore ReflectionClass and properties
351 1625
        $this->reflectionClass = $reflectionService->getClass($this->className);
352
353 1625
        if (! $this->reflectionClass) {
354
            return;
355
        }
356
357 1625
        $this->className = $this->reflectionClass->getName();
358
359 1625
        foreach ($this->declaredProperties as $property) {
360
            /** @var Property $property */
361 1624
            $property->wakeupReflection($reflectionService);
362
        }
363 1625
    }
364
365
    /**
366
     * Validates Identifier.
367
     *
368
     * @throws MappingException
369
     */
370 363
    public function validateIdentifier() : void
371
    {
372 363
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
373 28
            return;
374
        }
375
376
        // Verify & complete identifier mapping
377 363
        if (! $this->identifier) {
378 4
            throw MappingException::identifierRequired($this->className);
379
        }
380
381 359
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, function (Property $property) : bool {
382 359
            return $property instanceof FieldMetadata
383 359
                && $property->isPrimaryKey()
384 359
                && $property->hasValueGenerator();
385 359
        });
386
387 359
        if ($explicitlyGeneratedProperties && $this->isIdentifierComposite()) {
388
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
389
        }
390 359
    }
391
392
    /**
393
     * Validates association targets actually exist.
394
     *
395
     * @throws MappingException
396
     */
397 362
    public function validateAssociations() : void
398
    {
399 362
        array_map(
400 362
            function (Property $property) {
401 362
                if (! ($property instanceof AssociationMetadata)) {
402 359
                    return;
403
                }
404
405 249
                $targetEntity = $property->getTargetEntity();
406
407 249
                if (! class_exists($targetEntity)) {
408 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
409
                }
410 362
            },
411 362
            $this->declaredProperties
412
        );
413 361
    }
414
415
    /**
416
     * Validates lifecycle callbacks.
417
     *
418
     * @throws MappingException
419
     */
420 362
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
421
    {
422 362
        foreach ($this->lifecycleCallbacks as $callbacks) {
423
            /** @var array $callbacks */
424 11
            foreach ($callbacks as $callbackFuncName) {
425 11
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
426 11
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
427
                }
428
            }
429
        }
430 361
    }
431
432
    /**
433
     * Sets the change tracking policy used by this class.
434
     */
435 103
    public function setChangeTrackingPolicy(string $policy) : void
436
    {
437 103
        $this->changeTrackingPolicy = $policy;
438 103
    }
439
440
    /**
441
     * Checks whether a field is part of the identifier/primary key field(s).
442
     *
443
     * @param string $fieldName The field name.
444
     *
445
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
446
     */
447 1020
    public function isIdentifier(string $fieldName) : bool
448
    {
449 1020
        if (! $this->identifier) {
450 1
            return false;
451
        }
452
453 1019
        if (! $this->isIdentifierComposite()) {
454 1015
            return $fieldName === $this->identifier[0];
455
        }
456
457 93
        return in_array($fieldName, $this->identifier, true);
458
    }
459
460 1204
    public function isIdentifierComposite() : bool
461
    {
462 1204
        return isset($this->identifier[1]);
463
    }
464
465
    /**
466
     * Gets the result set mapping.
467
     *
468
     * @see ClassMetadata::$sqlResultSetMappings
469
     *
470
     * @param string $name The result set mapping name.
471
     *
472
     * @return mixed[]
473
     *
474
     * @throws MappingException
475
     */
476
    public function getSqlResultSetMapping($name)
477
    {
478
        if (! isset($this->sqlResultSetMappings[$name])) {
479
            throw MappingException::resultMappingNotFound($this->className, $name);
480
        }
481
482
        return $this->sqlResultSetMappings[$name];
483
    }
484
485
    /**
486
     * Gets all sql result set mappings of the class.
487
     *
488
     * @return mixed[][]
489
     */
490
    public function getSqlResultSetMappings()
491
    {
492
        return $this->sqlResultSetMappings;
493
    }
494
495
    /**
496
     * Validates & completes the basic mapping information for field mapping.
497
     *
498
     * @throws MappingException If something is wrong with the mapping.
499
     */
500 407
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
501
    {
502 407
        $fieldName  = $property->getName();
503 407
        $columnName = $property->getColumnName();
504
505 407
        if (empty($columnName)) {
506 343
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
507
508 343
            $property->setColumnName($columnName);
509
        }
510
511 407
        if (! $this->isMappedSuperclass) {
512 400
            $property->setTableName($this->getTableName());
513
        }
514
515
        // Check for already declared column
516 407
        if (isset($this->fieldNames[$columnName]) ||
517 407
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
518 2
            throw MappingException::duplicateColumnName($this->className, $columnName);
519
        }
520
521
        // Complete id mapping
522 406
        if ($property->isPrimaryKey()) {
523 391
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
524
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
525
            }
526
527 391
            if ($property->getType()->canRequireSQLConversion()) {
528
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($this->className, $property);
529
            }
530
531 391
            if (! in_array($fieldName, $this->identifier, true)) {
532 391
                $this->identifier[] = $fieldName;
533
            }
534
        }
535
536 406
        $this->fieldNames[$columnName] = $fieldName;
537 406
    }
538
539
    /**
540
     * Validates & completes the basic mapping information for field mapping.
541
     *
542
     * @throws MappingException If something is wrong with the mapping.
543
     */
544 20
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
545
    {
546 20
        $this->versionProperty = $property;
547
548 20
        $options = $property->getOptions();
549
550 20
        if (isset($options['default'])) {
551
            return;
552
        }
553
554 20
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'], true)) {
555 18
            $property->setOptions(array_merge($options, ['default' => 1]));
556
557 18
            return;
558
        }
559
560 3
        if ($property->getTypeName() === 'datetime') {
561 2
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
562
563 2
            return;
564
        }
565
566 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
567
    }
568
569
    /**
570
     * Validates & completes the basic mapping information that is common to all
571
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
572
     *
573
     * @throws MappingException If something is wrong with the mapping.
574
     * @throws CacheException   If entity is not cacheable.
575
     */
576 287
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
577
    {
578 287
        $fieldName    = $property->getName();
579 287
        $targetEntity = $property->getTargetEntity();
580
581 287
        if (! $targetEntity) {
582
            throw MappingException::missingTargetEntity($fieldName);
583
        }
584
585 287
        $property->setSourceEntity($this->className);
586 287
        $property->setTargetEntity($targetEntity);
587
588
        // Complete id mapping
589 287
        if ($property->isPrimaryKey()) {
590 47
            if ($property->isOrphanRemoval()) {
591 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
592
            }
593
594 46
            if (! in_array($property->getName(), $this->identifier, true)) {
595 46
                if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
596
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
597
                        $property->getTargetEntity(),
598
                        $this->className,
599
                        $fieldName
600
                    );
601
                }
602
603 46
                $this->identifier[] = $property->getName();
604
            }
605
606 46
            if ($this->cache && ! $property->getCache()) {
607 2
                throw CacheException::nonCacheableEntityAssociation($this->className, $fieldName);
608
            }
609
610 44
            if ($property instanceof ToManyAssociationMetadata) {
611 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
612
            }
613
        }
614
615
        // Cascades
616 283
        $cascadeTypes = ['remove', 'persist', 'refresh'];
617 283
        $cascades     = array_map('strtolower', $property->getCascade());
618
619 283
        if (in_array('all', $cascades, true)) {
620 6
            $cascades = $cascadeTypes;
621
        }
622
623 283
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
624 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
625
626 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
627
        }
628
629 282
        $property->setCascade($cascades);
630 282
    }
631
632
    /**
633
     * Validates & completes a to-one association mapping.
634
     *
635
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
636
     *
637
     * @throws \RuntimeException
638
     * @throws MappingException
639
     */
640 247
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
641
    {
642 247
        $fieldName = $property->getName();
643
644 247
        if ($property->isOwningSide()) {
645 243
            if (empty($property->getJoinColumns())) {
646
                // Apply default join column
647 86
                $property->addJoinColumn(new JoinColumnMetadata());
648
            }
649
650 243
            $uniqueConstraintColumns = [];
651
652 243
            foreach ($property->getJoinColumns() as $joinColumn) {
653
                /** @var JoinColumnMetadata $joinColumn */
654 243
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
655 112
                    if (count($property->getJoinColumns()) === 1) {
656 110
                        if (! $property->isPrimaryKey()) {
657 110
                            $joinColumn->setUnique(true);
658
                        }
659
                    } else {
660 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
661
                    }
662
                }
663
664 243
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
665
666 243
                if (! $joinColumn->getColumnName()) {
667 99
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
668
                }
669
670 243
                if (! $joinColumn->getReferencedColumnName()) {
671 86
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
672
                }
673
674 243
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
675
            }
676
677 243
            if ($uniqueConstraintColumns) {
678 2
                if (! $this->table) {
679
                    throw new \RuntimeException(
680
                        'ClassMetadata::setTable() has to be called before defining a one to one relationship.'
681
                    );
682
                }
683
684 2
                $this->table->addUniqueConstraint(
685
                    [
686 2
                        'name'    => sprintf('%s_uniq', $fieldName),
687 2
                        'columns' => $uniqueConstraintColumns,
688
                        'options' => [],
689
                        'flags'   => [],
690
                    ]
691
                );
692
            }
693
        }
694
695 247
        if ($property->isOrphanRemoval()) {
696 7
            $cascades = $property->getCascade();
697
698 7
            if (! in_array('remove', $cascades, true)) {
699 6
                $cascades[] = 'remove';
700
701 6
                $property->setCascade($cascades);
702
            }
703
704
            // @todo guilhermeblanco where is this used?
705
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
706
            //$property->setUnique(false);
707
        }
708
709 247
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
710 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
711
        }
712 246
    }
713
714
    /**
715
     * Validates & completes a to-many association mapping.
716
     *
717
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
718
     *
719
     * @throws MappingException
720
     */
721 172
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
722
    {
723
        // Do nothing
724 172
    }
725
726
    /**
727
     * Validates & completes a one-to-one association mapping.
728
     *
729
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
730
     */
731 129
    protected function validateAndCompleteOneToOneMapping(OneToOneAssociationMetadata $property)
732
    {
733
        // Do nothing
734 129
    }
735
736
    /**
737
     * Validates & completes a many-to-one association mapping.
738
     *
739
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
740
     *
741
     * @throws MappingException
742
     */
743 147
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
744
    {
745
        // A many-to-one mapping is essentially a one-one backreference
746 147
        if ($property->isOrphanRemoval()) {
747
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
748
        }
749 147
    }
750
751
    /**
752
     * Validates & completes a one-to-many association mapping.
753
     *
754
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
755
     *
756
     * @throws MappingException
757
     */
758 116
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
759
    {
760
        // OneToMany MUST have mappedBy
761 116
        if (! $property->getMappedBy()) {
762
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
763
        }
764
765 116
        if ($property->isOrphanRemoval()) {
766 19
            $cascades = $property->getCascade();
767
768 19
            if (! in_array('remove', $cascades, true)) {
769 16
                $cascades[] = 'remove';
770
771 16
                $property->setCascade($cascades);
772
            }
773
        }
774 116
    }
775
776
    /**
777
     * Validates & completes a many-to-many association mapping.
778
     *
779
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
780
     *
781
     * @throws MappingException
782
     */
783 109
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
784
    {
785 109
        if ($property->isOwningSide()) {
786
            // owning side MUST have a join table
787 97
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
788
789 97
            $property->setJoinTable($joinTable);
790
791 97
            if (! $joinTable->getName()) {
792 18
                $joinTableName = $this->namingStrategy->joinTableName(
793 18
                    $property->getSourceEntity(),
794 18
                    $property->getTargetEntity(),
795 18
                    $property->getName()
796
                );
797
798 18
                $joinTable->setName($joinTableName);
799
            }
800
801 97
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() === $property->getTargetEntity() && ! $joinTable->hasColumns();
802
803 97
            if (! $joinTable->getJoinColumns()) {
804 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
805 16
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
806 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
807 16
                $joinColumn           = new JoinColumnMetadata();
808
809 16
                $joinColumn->setColumnName($columnName);
810 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
811 16
                $joinColumn->setOnDelete('CASCADE');
812
813 16
                $joinTable->addJoinColumn($joinColumn);
814
            }
815
816 97
            if (! $joinTable->getInverseJoinColumns()) {
817 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
818 16
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
819 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
820 16
                $joinColumn           = new JoinColumnMetadata();
821
822 16
                $joinColumn->setColumnName($columnName);
823 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
824 16
                $joinColumn->setOnDelete('CASCADE');
825
826 16
                $joinTable->addInverseJoinColumn($joinColumn);
827
            }
828
829 97
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
830
                /** @var JoinColumnMetadata $joinColumn */
831 97
                if (! $joinColumn->getReferencedColumnName()) {
832 2
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
833
                }
834
835 97
                $referencedColumnName = $joinColumn->getReferencedColumnName();
836
837 97
                if (! $joinColumn->getColumnName()) {
838 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
839 2
                        $property->getSourceEntity(),
840 2
                        $referencedColumnName
841
                    );
842
843 97
                    $joinColumn->setColumnName($columnName);
844
                }
845
            }
846
847 97
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
848
                /** @var JoinColumnMetadata $inverseJoinColumn */
849 97
                if (! $inverseJoinColumn->getReferencedColumnName()) {
850 2
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
851
                }
852
853 97
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
854
855 97
                if (! $inverseJoinColumn->getColumnName()) {
856 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
857 2
                        $property->getTargetEntity(),
858 2
                        $referencedColumnName
859
                    );
860
861 97
                    $inverseJoinColumn->setColumnName($columnName);
862
                }
863
            }
864
        }
865 109
    }
866
867
    /**
868
     * {@inheritDoc}
869
     */
870 396
    public function getIdentifierFieldNames()
871
    {
872 396
        return $this->identifier;
873
    }
874
875
    /**
876
     * Gets the name of the single id field. Note that this only works on
877
     * entity classes that have a single-field pk.
878
     *
879
     * @return string
880
     *
881
     * @throws MappingException If the class has a composite primary key.
882
     */
883 149
    public function getSingleIdentifierFieldName()
884
    {
885 149
        if ($this->isIdentifierComposite()) {
886 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
887
        }
888
889 148
        if (! isset($this->identifier[0])) {
890 1
            throw MappingException::noIdDefined($this->className);
891
        }
892
893 147
        return $this->identifier[0];
894
    }
895
896
    /**
897
     * INTERNAL:
898
     * Sets the mapped identifier/primary key fields of this class.
899
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
900
     *
901
     * @param mixed[] $identifier
902
     */
903 100
    public function setIdentifier(array $identifier)
904
    {
905 100
        $this->identifier = $identifier;
906 100
    }
907
908
    /**
909
     * {@inheritDoc}
910
     */
911 1045
    public function getIdentifier()
912
    {
913 1045
        return $this->identifier;
914
    }
915
916
    /**
917
     * {@inheritDoc}
918
     */
919 187
    public function hasField($fieldName)
920
    {
921 187
        return isset($this->declaredProperties[$fieldName])
922 187
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
923
    }
924
925
    /**
926
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
927
     *
928
     * @return ColumnMetadata[]
929
     */
930 440
    public function getIdentifierColumns(EntityManagerInterface $em) : array
931
    {
932 440
        $columns = [];
933
934 440
        foreach ($this->identifier as $idProperty) {
935 440
            $property = $this->getProperty($idProperty);
936
937 440
            if ($property instanceof FieldMetadata) {
938 434
                $columns[$property->getColumnName()] = $property;
939
940 434
                continue;
941
            }
942
943
            /** @var AssociationMetadata $property */
944
945
            // Association defined as Id field
946 25
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
947
948 25
            if (! $property->isOwningSide()) {
949
                $property    = $targetClass->getProperty($property->getMappedBy());
950
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
951
            }
952
953 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
954
                ? $property->getJoinTable()->getInverseJoinColumns()
955 25
                : $property->getJoinColumns()
956
            ;
957
958 25
            foreach ($joinColumns as $joinColumn) {
959
                /** @var JoinColumnMetadata $joinColumn */
960 25
                $columnName           = $joinColumn->getColumnName();
961 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
962
963 25
                if (! $joinColumn->getType()) {
964 13
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
965
                }
966
967 25
                $columns[$columnName] = $joinColumn;
968
            }
969
        }
970
971 440
        return $columns;
972
    }
973
974
    /**
975
     * Gets the name of the primary table.
976
     */
977 1583
    public function getTableName() : ?string
978
    {
979 1583
        return $this->table->getName();
980
    }
981
982
    /**
983
     * Gets primary table's schema name.
984
     */
985 14
    public function getSchemaName() : ?string
986
    {
987 14
        return $this->table->getSchema();
988
    }
989
990
    /**
991
     * Gets the table name to use for temporary identifier tables of this class.
992
     */
993 7
    public function getTemporaryIdTableName() : string
994
    {
995 7
        $schema = $this->getSchemaName() === null
996 6
            ? ''
997 7
            : $this->getSchemaName() . '_'
998
        ;
999
1000
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
1001 7
        return $schema . $this->getTableName() . '_id_tmp';
1002
    }
1003
1004
    /**
1005
     * Sets the mapped subclasses of this class.
1006
     *
1007
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
1008
     *
1009
     * @param string[] $subclasses The names of all mapped subclasses.
1010
     */
1011 4
    public function setSubclasses(array $subclasses) : void
1012
    {
1013 4
        foreach ($subclasses as $subclass) {
1014 3
            $this->subClasses[] = $subclass;
1015
        }
1016 4
    }
1017
1018
    /**
1019
     * @return string[]
1020
     */
1021 1076
    public function getSubClasses() : array
1022
    {
1023 1076
        return $this->subClasses;
1024
    }
1025
1026
    /**
1027
     * Sets the inheritance type used by the class and its subclasses.
1028
     *
1029
     * @param int $type
1030
     *
1031
     * @throws MappingException
1032
     */
1033 118
    public function setInheritanceType($type) : void
1034
    {
1035 118
        if (! $this->isInheritanceType($type)) {
1036
            throw MappingException::invalidInheritanceType($this->className, $type);
1037
        }
1038
1039 118
        $this->inheritanceType = $type;
1040 118
    }
1041
1042
    /**
1043
     * Sets the override property mapping for an entity relationship.
1044
     *
1045
     * @throws \RuntimeException
1046
     * @throws MappingException
1047
     * @throws CacheException
1048
     */
1049 12
    public function setPropertyOverride(Property $property) : void
1050
    {
1051 12
        $fieldName = $property->getName();
1052
1053 12
        if (! isset($this->declaredProperties[$fieldName])) {
1054 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1055
        }
1056
1057 10
        $originalProperty          = $this->getProperty($fieldName);
1058 10
        $originalPropertyClassName = get_class($originalProperty);
1059
1060
        // If moving from transient to persistent, assume it's a new property
1061 10
        if ($originalPropertyClassName === TransientMetadata::class) {
1062 1
            unset($this->declaredProperties[$fieldName]);
1063
1064 1
            $this->addProperty($property);
1065
1066 1
            return;
1067
        }
1068
1069
        // Do not allow to change property type
1070 9
        if ($originalPropertyClassName !== get_class($property)) {
1071
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1072
        }
1073
1074
        // Do not allow to change version property
1075 9
        if ($originalProperty instanceof VersionFieldMetadata) {
1076
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1077
        }
1078
1079 9
        unset($this->declaredProperties[$fieldName]);
1080
1081 9
        if ($property instanceof FieldMetadata) {
1082
            // Unset defined fieldName prior to override
1083 5
            unset($this->fieldNames[$originalProperty->getColumnName()]);
1084
1085
            // Revert what should not be allowed to change
1086 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1087 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1088 9
        } elseif ($property instanceof AssociationMetadata) {
1089
            // Unset all defined fieldNames prior to override
1090 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1091 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1092 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1093
                }
1094
            }
1095
1096
            // Override what it should be allowed to change
1097 9
            if ($property->getInversedBy()) {
1098 2
                $originalProperty->setInversedBy($property->getInversedBy());
1099
            }
1100
1101 9
            if ($property->getFetchMode() !== $originalProperty->getFetchMode()) {
1102 2
                $originalProperty->setFetchMode($property->getFetchMode());
1103
            }
1104
1105 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) {
1106 5
                $originalProperty->setJoinColumns($property->getJoinColumns());
1107 8
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) {
1108 4
                $originalProperty->setJoinTable($property->getJoinTable());
1109
            }
1110
1111 9
            $property = $originalProperty;
1112
        }
1113
1114 9
        $this->addProperty($property);
1115 9
    }
1116
1117
    /**
1118
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1119
     *
1120
     * @return bool
1121
     */
1122 337
    public function isRootEntity()
1123
    {
1124 337
        return $this->className === $this->getRootClassName();
1125
    }
1126
1127
    /**
1128
     * Checks whether a mapped field is inherited from a superclass.
1129
     *
1130
     * @param string $fieldName
1131
     *
1132
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1133
     */
1134 621
    public function isInheritedProperty($fieldName)
1135
    {
1136 621
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1137
1138 621
        return ! ($declaringClass->className === $this->className);
1139
    }
1140
1141
    /**
1142
     * {@inheritdoc}
1143
     */
1144 441
    public function setTable(TableMetadata $table) : void
1145
    {
1146 441
        $this->table = $table;
1147
1148 441
        if (empty($table->getName())) {
1149
            $table->setName($this->namingStrategy->classToTableName($this->className));
1150
        }
1151 441
    }
1152
1153
    /**
1154
     * Checks whether the given type identifies an inheritance type.
1155
     *
1156
     * @param int $type
1157
     *
1158
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1159
     */
1160 118
    private function isInheritanceType($type)
1161
    {
1162 118
        return $type === InheritanceType::NONE
1163 92
            || $type === InheritanceType::SINGLE_TABLE
1164 50
            || $type === InheritanceType::JOINED
1165 118
            || $type === InheritanceType::TABLE_PER_CLASS;
1166
    }
1167
1168 909
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1169
    {
1170 909
        foreach ($this->declaredProperties as $property) {
1171 909
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1172 909
                return $property;
1173
            }
1174
        }
1175
1176
        return null;
1177
    }
1178
1179
    /**
1180
     * Add a property mapping.
1181
     *
1182
     * @throws \RuntimeException
1183
     * @throws MappingException
1184
     * @throws CacheException
1185
     */
1186 431
    public function addProperty(Property $property)
1187
    {
1188 431
        $fieldName = $property->getName();
1189
1190
        // Check for empty field name
1191 431
        if (empty($fieldName)) {
1192 1
            throw MappingException::missingFieldName($this->className);
1193
        }
1194
1195 430
        $property->setDeclaringClass($this);
1196
1197
        switch (true) {
1198 430
            case ($property instanceof VersionFieldMetadata):
1199 20
                $this->validateAndCompleteFieldMapping($property);
1200 20
                $this->validateAndCompleteVersionFieldMapping($property);
1201 19
                break;
1202
1203 429
            case ($property instanceof FieldMetadata):
1204 406
                $this->validateAndCompleteFieldMapping($property);
1205 405
                break;
1206
1207 290
            case ($property instanceof OneToOneAssociationMetadata):
1208 131
                $this->validateAndCompleteAssociationMapping($property);
1209 130
                $this->validateAndCompleteToOneAssociationMetadata($property);
1210 129
                $this->validateAndCompleteOneToOneMapping($property);
1211 129
                break;
1212
1213 226
            case ($property instanceof OneToManyAssociationMetadata):
1214 116
                $this->validateAndCompleteAssociationMapping($property);
1215 116
                $this->validateAndCompleteToManyAssociationMetadata($property);
1216 116
                $this->validateAndCompleteOneToManyMapping($property);
1217 116
                break;
1218
1219 222
            case ($property instanceof ManyToOneAssociationMetadata):
1220 150
                $this->validateAndCompleteAssociationMapping($property);
1221 147
                $this->validateAndCompleteToOneAssociationMetadata($property);
1222 147
                $this->validateAndCompleteManyToOneMapping($property);
1223 147
                break;
1224
1225 126
            case ($property instanceof ManyToManyAssociationMetadata):
1226 110
                $this->validateAndCompleteAssociationMapping($property);
1227 109
                $this->validateAndCompleteToManyAssociationMetadata($property);
1228 109
                $this->validateAndCompleteManyToManyMapping($property);
1229 109
                break;
1230
1231
            default:
1232
                // Transient properties are ignored on purpose here! =)
1233 30
                break;
1234
        }
1235
1236 422
        $this->addDeclaredProperty($property);
1237 422
    }
1238
1239
    /**
1240
     * INTERNAL:
1241
     * Adds a property mapping without completing/validating it.
1242
     * This is mainly used to add inherited property mappings to derived classes.
1243
     */
1244 98
    public function addInheritedProperty(Property $property)
1245
    {
1246 98
        $inheritedProperty = clone $property;
1247 98
        $declaringClass    = $property->getDeclaringClass();
1248
1249 98
        if ($inheritedProperty instanceof FieldMetadata) {
1250 97
            if (! $declaringClass->isMappedSuperclass) {
1251 75
                $inheritedProperty->setTableName($property->getTableName());
1252
            }
1253
1254 97
            $this->fieldNames[$property->getColumnName()] = $property->getName();
1255 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
1256 42
            if ($declaringClass->isMappedSuperclass) {
1257 10
                $inheritedProperty->setSourceEntity($this->className);
1258
            }
1259
1260
            // Need to add inherited fieldNames
1261 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1262 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1263
                    /** @var JoinColumnMetadata $joinColumn */
1264 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1265
                }
1266
            }
1267
        }
1268
1269 98
        if (isset($this->declaredProperties[$property->getName()])) {
1270 1
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
1271
        }
1272
1273 98
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1274
1275 98
        if ($inheritedProperty instanceof VersionFieldMetadata) {
1276 4
            $this->versionProperty = $inheritedProperty;
1277
        }
1278 98
    }
1279
1280
    /**
1281
     * INTERNAL:
1282
     * Adds a sql result set mapping to this class.
1283
     *
1284
     * @param mixed[] $resultMapping
1285
     *
1286
     * @throws MappingException
1287
     */
1288
    public function addSqlResultSetMapping(array $resultMapping)
1289
    {
1290
        if (! isset($resultMapping['name'])) {
1291
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->className);
1292
        }
1293
1294
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
1295
            throw MappingException::duplicateResultSetMapping($this->className, $resultMapping['name']);
1296
        }
1297
1298
        if (isset($resultMapping['entities'])) {
1299
            foreach ($resultMapping['entities'] as $key => $entityResult) {
1300
                if (! isset($entityResult['entityClass'])) {
1301
                    throw MappingException::missingResultSetMappingEntity($this->className, $resultMapping['name']);
1302
                }
1303
1304
                $entityClassName                                = $entityResult['entityClass'];
1305
                $resultMapping['entities'][$key]['entityClass'] = $entityClassName;
1306
1307
                if (isset($entityResult['fields'])) {
1308
                    foreach ($entityResult['fields'] as $k => $field) {
1309
                        if (! isset($field['name'])) {
1310
                            throw MappingException::missingResultSetMappingFieldName($this->className, $resultMapping['name']);
1311
                        }
1312
1313
                        if (! isset($field['column'])) {
1314
                            $fieldName = $field['name'];
1315
1316
                            if (strpos($fieldName, '.')) {
1317
                                list(, $fieldName) = explode('.', $fieldName);
1318
                            }
1319
1320
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
1321
                        }
1322
                    }
1323
                }
1324
            }
1325
        }
1326
1327
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
0 ignored issues
show
Bug Best Practice introduced by
The property sqlResultSetMappings does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1328
    }
1329
1330
    /**
1331
     * Registers a custom repository class for the entity class.
1332
     *
1333
     * @param string|null $repositoryClassName The class name of the custom mapper.
1334
     */
1335 31
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1336
    {
1337 31
        $this->customRepositoryClassName = $repositoryClassName;
1338 31
    }
1339
1340 164
    public function getCustomRepositoryClassName() : ?string
1341
    {
1342 164
        return $this->customRepositoryClassName;
1343
    }
1344
1345
    /**
1346
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1347
     *
1348
     * @param string $lifecycleEvent
1349
     *
1350
     * @return bool
1351
     */
1352
    public function hasLifecycleCallbacks($lifecycleEvent)
1353
    {
1354
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1355
    }
1356
1357
    /**
1358
     * Gets the registered lifecycle callbacks for an event.
1359
     *
1360
     * @param string $event
1361
     *
1362
     * @return string[]
1363
     */
1364
    public function getLifecycleCallbacks($event)
1365
    {
1366
        return $this->lifecycleCallbacks[$event] ?? [];
1367
    }
1368
1369
    /**
1370
     * Adds a lifecycle callback for entities of this class.
1371
     *
1372
     * @param string $callback
1373
     * @param string $event
1374
     */
1375 17
    public function addLifecycleCallback($callback, $event)
1376
    {
1377 17
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) {
1378 3
            return;
1379
        }
1380
1381 17
        $this->lifecycleCallbacks[$event][] = $callback;
1382 17
    }
1383
1384
    /**
1385
     * Sets the lifecycle callbacks for entities of this class.
1386
     * Any previously registered callbacks are overwritten.
1387
     *
1388
     * @param string[][] $callbacks
1389
     */
1390 98
    public function setLifecycleCallbacks(array $callbacks) : void
1391
    {
1392 98
        $this->lifecycleCallbacks = $callbacks;
1393 98
    }
1394
1395
    /**
1396
     * Adds a entity listener for entities of this class.
1397
     *
1398
     * @param string $eventName The entity lifecycle event.
1399
     * @param string $class     The listener class.
1400
     * @param string $method    The listener callback method.
1401
     *
1402
     * @throws MappingException
1403
     */
1404 17
    public function addEntityListener(string $eventName, string $class, string $method) : void
1405
    {
1406
        $listener = [
1407 17
            'class'  => $class,
1408 17
            'method' => $method,
1409
        ];
1410
1411 17
        if (! class_exists($class)) {
1412 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1413
        }
1414
1415 16
        if (! method_exists($class, $method)) {
1416 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1417
        }
1418
1419 15
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
1420 1
            throw MappingException::duplicateEntityListener($class, $method, $this->className);
1421
        }
1422
1423 15
        $this->entityListeners[$eventName][] = $listener;
1424 15
    }
1425
1426
    /**
1427
     * Sets the discriminator column definition.
1428
     *
1429
     * @throws MappingException
1430
     *
1431
     * @see getDiscriminatorColumn()
1432
     */
1433 94
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1434
    {
1435 94
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1436 1
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1437
        }
1438
1439 93
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1440
1441 93
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1442
1443 93
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1444
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1445
        }
1446
1447 93
        $this->discriminatorColumn = $discriminatorColumn;
1448 93
    }
1449
1450
    /**
1451
     * Sets the discriminator values used by this class.
1452
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1453
     *
1454
     * @param string[] $map
1455
     *
1456
     * @throws MappingException
1457
     */
1458 91
    public function setDiscriminatorMap(array $map) : void
1459
    {
1460 91
        foreach ($map as $value => $className) {
1461 91
            $this->addDiscriminatorMapClass($value, $className);
1462
        }
1463 91
    }
1464
1465
    /**
1466
     * Adds one entry of the discriminator map with a new class and corresponding name.
1467
     *
1468
     * @param string|int $name
1469
     *
1470
     * @throws MappingException
1471
     */
1472 91
    public function addDiscriminatorMapClass($name, string $className) : void
1473
    {
1474 91
        $this->discriminatorMap[$name] = $className;
1475
1476 91
        if ($this->className === $className) {
1477 77
            $this->discriminatorValue = $name;
1478
1479 77
            return;
1480
        }
1481
1482 90
        if (! (class_exists($className) || interface_exists($className))) {
1483
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1484
        }
1485
1486 90
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1487 85
            $this->subClasses[] = $className;
1488
        }
1489 90
    }
1490
1491 1024
    public function getValueGenerationPlan() : ValueGenerationPlan
1492
    {
1493 1024
        return $this->valueGenerationPlan;
1494
    }
1495
1496 363
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1497
    {
1498 363
        $this->valueGenerationPlan = $valueGenerationPlan;
1499 363
    }
1500
1501
    /**
1502
     * Checks whether the class has a named native query with the given query name.
1503
     *
1504
     * @param string $name
1505
     */
1506
    public function hasSqlResultSetMapping($name) : bool
1507
    {
1508
        return isset($this->sqlResultSetMappings[$name]);
1509
    }
1510
1511
    /**
1512
     * Marks this class as read only, no change tracking is applied to it.
1513
     */
1514 2
    public function asReadOnly() : void
1515
    {
1516 2
        $this->readOnly = true;
1517 2
    }
1518
1519
    /**
1520
     * Whether this class is read only or not.
1521
     */
1522 444
    public function isReadOnly() : bool
1523
    {
1524 444
        return $this->readOnly;
1525
    }
1526
1527 1084
    public function isVersioned() : bool
1528
    {
1529 1084
        return $this->versionProperty !== null;
1530
    }
1531
1532
    /**
1533
     * Map Embedded Class
1534
     *
1535
     * @param mixed[] $mapping
1536
     *
1537
     * @throws MappingException
1538
     */
1539
    public function mapEmbedded(array $mapping) : void
1540
    {
1541
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
1542
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1543
        }
1544
1545
        $this->embeddedClasses[$mapping['fieldName']] = [
1546
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1547
            'columnPrefix'   => $mapping['columnPrefix'],
1548
            'declaredField'  => $mapping['declaredField'] ?? null,
1549
            'originalField'  => $mapping['originalField'] ?? null,
1550
            'declaringClass' => $this,
1551
        ];*/
1552
    }
1553
1554
    /**
1555
     * Inline the embeddable class
1556
     *
1557
     * @param string $property
1558
     */
1559
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
1560
    {
1561
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1562
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1563
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1564
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1565
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1566
                ? $property . '.' . $fieldMapping['declaredField']
1567
                : $property;
1568
1569
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1570
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1571
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1572
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1573
                    $property,
1574
                    $fieldMapping['columnName'],
1575
                    $this->reflectionClass->getName(),
1576
                    $embeddable->reflectionClass->getName()
1577
                );
1578
            }
1579
1580
            $this->mapField($fieldMapping);
1581
        }*/
1582
    }
1583
}
1584