Failed Conditions
Push — master ( 047620...906c14 )
by Guilherme
08:56
created

ClassMetadata::setChangeTrackingPolicy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

225
        $this->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
226 98
        $this->setIdentifier($parent->identifier);
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
238 98
        if ($parent->cache) {
239 3
            $this->setCache(clone $parent->cache);
240
        }
241
242 98
        if (! empty($parent->lifecycleCallbacks)) {
243 5
            $this->lifecycleCallbacks = $parent->lifecycleCallbacks;
244
        }
245
246 98
        if (! empty($parent->entityListeners)) {
247 7
            $this->entityListeners = $parent->entityListeners;
248
        }
249 98
    }
250
251
    public function setClassName(string $className)
252
    {
253
        $this->className = $className;
254
    }
255
256
    public function getColumnsIterator() : ArrayIterator
257
    {
258
        $iterator = parent::getColumnsIterator();
259
260
        if ($this->discriminatorColumn) {
261
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), $this->discriminatorColumn);
0 ignored issues
show
Bug introduced by
$this->discriminatorColumn of type Doctrine\ORM\Mapping\DiscriminatorColumnMetadata is incompatible with the type string expected by parameter $newval of ArrayIterator::offsetSet(). ( Ignorable by Annotation )

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

261
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), /** @scrutinizer ignore-type */ $this->discriminatorColumn);
Loading history...
262
        }
263
264
        return $iterator;
265
    }
266
267 11
    public function getAncestorsIterator() : ArrayIterator
268
    {
269 11
        $ancestors = new ArrayIterator();
270 11
        $parent    = $this;
271
272 11
        while (($parent = $parent->parent) !== null) {
273 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
274 1
                continue;
275
            }
276
277 7
            $ancestors->append($parent);
278
        }
279
280 11
        return $ancestors;
281
    }
282
283 1260
    public function getRootClassName() : string
284
    {
285 1260
        return $this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass
286 402
            ? $this->parent->getRootClassName()
287 1260
            : $this->className;
288
    }
289
290
    /**
291
     * Handles metadata cloning nicely.
292
     */
293 13
    public function __clone()
294
    {
295 13
        if ($this->cache) {
296 12
            $this->cache = clone $this->cache;
297
        }
298
299 13
        foreach ($this->declaredProperties as $name => $property) {
300 13
            $this->declaredProperties[$name] = clone $property;
301
        }
302 13
    }
303
304
    /**
305
     * Creates a string representation of this instance.
306
     *
307
     * @return string The string representation of this instance.
308
     *
309
     * @todo Construct meaningful string representation.
310
     */
311
    public function __toString()
312
    {
313
        return self::class . '@' . spl_object_id($this);
314
    }
315
316
    /**
317
     * Determines which fields get serialized.
318
     *
319
     * It is only serialized what is necessary for best unserialization performance.
320
     * That means any metadata properties that are not set or empty or simply have
321
     * their default value are NOT serialized.
322
     *
323
     * Parts that are also NOT serialized because they can not be properly unserialized:
324
     * - reflectionClass
325
     *
326
     * @return string[] The names of all the fields that should be serialized.
327
     */
328 5
    public function __sleep()
329
    {
330 5
        $serialized = [];
331
332
        // This metadata is always serialized/cached.
333 5
        $serialized = array_merge($serialized, [
334 5
            'declaredProperties',
335
            'fieldNames',
336
            //'embeddedClasses',
337
            'identifier',
338
            'className',
339
            'parent',
340
            'table',
341
            'valueGenerationPlan',
342
        ]);
343
344
        // The rest of the metadata is only serialized if necessary.
345 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
346
            $serialized[] = 'changeTrackingPolicy';
347
        }
348
349 5
        if ($this->customRepositoryClassName) {
350 1
            $serialized[] = 'customRepositoryClassName';
351
        }
352
353 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
354 1
            $serialized[] = 'inheritanceType';
355 1
            $serialized[] = 'discriminatorColumn';
356 1
            $serialized[] = 'discriminatorValue';
357 1
            $serialized[] = 'discriminatorMap';
358 1
            $serialized[] = 'subClasses';
359
        }
360
361 5
        if ($this->isMappedSuperclass) {
362
            $serialized[] = 'isMappedSuperclass';
363
        }
364
365 5
        if ($this->isEmbeddedClass) {
366
            $serialized[] = 'isEmbeddedClass';
367
        }
368
369 5
        if ($this->isVersioned()) {
370
            $serialized[] = 'versionProperty';
371
        }
372
373 5
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array<mixed,string[]> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
374
            $serialized[] = 'lifecycleCallbacks';
375
        }
376
377 5
        if ($this->entityListeners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityListeners of type array<mixed,array<mixed,mixed>> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
378 1
            $serialized[] = 'entityListeners';
379
        }
380
381 5
        if ($this->cache) {
382
            $serialized[] = 'cache';
383
        }
384
385 5
        if ($this->readOnly) {
386 1
            $serialized[] = 'readOnly';
387
        }
388
389 5
        return $serialized;
390
    }
391
392
    /**
393
     * Restores some state that can not be serialized/unserialized.
394
     */
395 1634
    public function wakeupReflection(ReflectionService $reflectionService) : void
396
    {
397
        // Restore ReflectionClass and properties
398 1634
        $this->reflectionClass = $reflectionService->getClass($this->className);
399
400 1634
        if (! $this->reflectionClass) {
401
            return;
402
        }
403
404 1634
        $this->className = $this->reflectionClass->getName();
405
406 1634
        foreach ($this->declaredProperties as $property) {
407
            /** @var Property $property */
408 1633
            $property->wakeupReflection($reflectionService);
409
        }
410 1634
    }
411
412
    /**
413
     * Sets the change tracking policy used by this class.
414
     */
415 104
    public function setChangeTrackingPolicy(string $policy) : void
416
    {
417 104
        $this->changeTrackingPolicy = $policy;
418 104
    }
419
420
    /**
421
     * Checks whether a field is part of the identifier/primary key field(s).
422
     *
423
     * @param string $fieldName The field name.
424
     *
425
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
426
     */
427 1027
    public function isIdentifier(string $fieldName) : bool
428
    {
429 1027
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
430 1
            return false;
431
        }
432
433 1026
        if (! $this->isIdentifierComposite()) {
434 1022
            return $fieldName === $this->identifier[0];
435
        }
436
437 93
        return in_array($fieldName, $this->identifier, true);
438
    }
439
440 1213
    public function isIdentifierComposite() : bool
441
    {
442 1213
        return isset($this->identifier[1]);
443
    }
444
445
    /**
446
     * Validates Identifier.
447
     *
448
     * @throws MappingException
449
     */
450 369
    public function validateIdentifier() : void
451
    {
452 369
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
453 27
            return;
454
        }
455
456
        // Verify & complete identifier mapping
457 369
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
458 4
            throw MappingException::identifierRequired($this->className);
459
        }
460
461
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, static function (Property $property) : bool {
462 365
            return $property instanceof FieldMetadata
463 365
                && $property->isPrimaryKey()
464 365
                && $property->hasValueGenerator();
465 365
        });
466
467 365
        if ($explicitlyGeneratedProperties && $this->isIdentifierComposite()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $explicitlyGeneratedProperties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
468
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
469
        }
470 365
    }
471
472
    /**
473
     * Validates association targets actually exist.
474
     *
475
     * @throws MappingException
476
     */
477 368
    public function validateAssociations() : void
478
    {
479 368
        array_map(
480
            function (Property $property) {
481 368
                if (! ($property instanceof AssociationMetadata)) {
482 365
                    return;
483
                }
484
485 249
                $targetEntity = $property->getTargetEntity();
486
487 249
                if (! class_exists($targetEntity)) {
488 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
489
                }
490 368
            },
491 368
            $this->declaredProperties
492
        );
493 367
    }
494
495
    /**
496
     * Validates lifecycle callbacks.
497
     *
498
     * @throws MappingException
499
     */
500 368
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
501
    {
502 368
        foreach ($this->lifecycleCallbacks as $callbacks) {
503
            /** @var array $callbacks */
504 10
            foreach ($callbacks as $callbackFuncName) {
505 10
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
506 1
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
507
                }
508
            }
509
        }
510 367
    }
511
512
    /**
513
     * Validates & completes the basic mapping information for field mapping.
514
     *
515
     * @throws MappingException If something is wrong with the mapping.
516
     */
517 21
    protected function validateAndCompleteVersionFieldMapping(FieldMetadata $property)
518
    {
519 21
        $this->versionProperty = $property;
520
521 21
        $options = $property->getOptions();
522
523 21
        if (isset($options['default'])) {
524
            return;
525
        }
526
527 21
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'], true)) {
528 19
            $property->setOptions(array_merge($options, ['default' => 1]));
529
530 19
            return;
531
        }
532
533 3
        if (in_array($property->getTypeName(), ['datetime', 'datetime_immutable', 'datetimetz', 'datetimetz_immutable'], true)) {
534 2
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
535
536 2
            return;
537
        }
538
539 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
540
    }
541
542
    /**
543
     * Validates & completes the basic mapping information that is common to all
544
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
545
     *
546
     * @throws MappingException If something is wrong with the mapping.
547
     * @throws CacheException   If entity is not cacheable.
548
     */
549 289
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
550
    {
551 289
        $fieldName    = $property->getName();
552 289
        $targetEntity = $property->getTargetEntity();
553
554 289
        if (! $targetEntity) {
555
            throw MappingException::missingTargetEntity($fieldName);
556
        }
557
558 289
        $property->setSourceEntity($this->className);
559 289
        $property->setTargetEntity($targetEntity);
560
561
        // Complete id mapping
562 289
        if ($property->isPrimaryKey()) {
563 47
            if ($property->isOrphanRemoval()) {
564 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
565
            }
566
567 46
            if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
568
                throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
569
                    $property->getTargetEntity(),
570
                    $this->className,
571
                    $fieldName
572
                );
573
            }
574
575 46
            if ($this->cache && ! $property->getCache()) {
576 2
                throw NonCacheableEntityAssociation::fromEntityAndField($this->className, $fieldName);
577
            }
578
579 44
            if ($property instanceof ToManyAssociationMetadata) {
580 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
581
            }
582
583 43
            if (! in_array($property->getName(), $this->identifier, true)) {
584 43
                $this->identifier[] = $property->getName();
585
            }
586
        }
587
588
        // Cascades
589 285
        $cascadeTypes = ['remove', 'persist', 'refresh'];
590 285
        $cascades     = array_map('strtolower', $property->getCascade());
591
592 285
        if (in_array('all', $cascades, true)) {
593 6
            $cascades = $cascadeTypes;
594
        }
595
596 285
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
597 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
598
599 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
600
        }
601
602 284
        $property->setCascade($cascades);
603 284
    }
604
605
    /**
606
     * Validates & completes a to-one association mapping.
607
     *
608
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
609
     *
610
     * @throws RuntimeException
611
     * @throws MappingException
612
     */
613 247
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
614
    {
615 247
        $fieldName = $property->getName();
616
617 247
        if ($property->isOwningSide()) {
618 243
            if (empty($property->getJoinColumns())) {
619
                // Apply default join column
620 86
                $property->addJoinColumn(new JoinColumnMetadata());
621
            }
622
623 243
            $uniqueConstraintColumns = [];
624
625 243
            foreach ($property->getJoinColumns() as $joinColumn) {
626
                /** @var JoinColumnMetadata $joinColumn */
627 243
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
628 110
                    if (count($property->getJoinColumns()) === 1) {
629 108
                        if (! $property->isPrimaryKey()) {
630 108
                            $joinColumn->setUnique(true);
631
                        }
632
                    } else {
633 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
634
                    }
635
                }
636
637 243
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
638
639 243
                if (! $joinColumn->getColumnName()) {
640 98
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
641
                }
642
643 243
                if (! $joinColumn->getReferencedColumnName()) {
644 86
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
645
                }
646
647 243
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
648
            }
649
650 243
            if ($uniqueConstraintColumns) {
651 2
                if (! $this->table) {
652
                    throw new RuntimeException(
653
                        'ClassMetadata::setTable() has to be called before defining a one to one relationship.'
654
                    );
655
                }
656
657 2
                $this->table->addUniqueConstraint(
658
                    [
659 2
                        'name'    => sprintf('%s_uniq', $fieldName),
660 2
                        'columns' => $uniqueConstraintColumns,
661
                        'options' => [],
662
                        'flags'   => [],
663
                    ]
664
                );
665
            }
666
        }
667
668 247
        if ($property->isOrphanRemoval()) {
669 7
            $cascades = $property->getCascade();
670
671 7
            if (! in_array('remove', $cascades, true)) {
672 6
                $cascades[] = 'remove';
673
674 6
                $property->setCascade($cascades);
675
            }
676
677
            // @todo guilhermeblanco where is this used?
678
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
679
            //$property->setUnique(false);
680
        }
681
682 247
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
683 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
684
        }
685 246
    }
686
687
    /**
688
     * Validates & completes a to-many association mapping.
689
     *
690
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
691
     *
692
     * @throws MappingException
693
     */
694 175
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

694
    protected function validateAndCompleteToManyAssociationMetadata(/** @scrutinizer ignore-unused */ ToManyAssociationMetadata $property)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
695
    {
696
        // Do nothing
697 175
    }
698
699
    /**
700
     * Validates & completes a one-to-one association mapping.
701
     *
702
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
703
     */
704 127
    protected function validateAndCompleteOneToOneMapping(OneToOneAssociationMetadata $property)
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

704
    protected function validateAndCompleteOneToOneMapping(/** @scrutinizer ignore-unused */ OneToOneAssociationMetadata $property)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
705
    {
706
        // Do nothing
707 127
    }
708
709
    /**
710
     * Validates & completes a many-to-one association mapping.
711
     *
712
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
713
     *
714
     * @throws MappingException
715
     */
716 149
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
717
    {
718
        // A many-to-one mapping is essentially a one-one backreference
719 149
        if ($property->isOrphanRemoval()) {
720
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
721
        }
722 149
    }
723
724
    /**
725
     * Validates & completes a one-to-many association mapping.
726
     *
727
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
728
     *
729
     * @throws MappingException
730
     */
731 118
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
732
    {
733
        // OneToMany MUST have mappedBy
734 118
        if (! $property->getMappedBy()) {
735
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
736
        }
737
738 118
        if ($property->isOrphanRemoval()) {
739 19
            $cascades = $property->getCascade();
740
741 19
            if (! in_array('remove', $cascades, true)) {
742 16
                $cascades[] = 'remove';
743
744 16
                $property->setCascade($cascades);
745
            }
746
        }
747 118
    }
748
749
    /**
750
     * Validates & completes a many-to-many association mapping.
751
     *
752
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
753
     *
754
     * @throws MappingException
755
     */
756 110
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
757
    {
758 110
        if ($property->isOwningSide()) {
759
            // owning side MUST have a join table
760 97
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
761
762 97
            $property->setJoinTable($joinTable);
763
764 97
            if (! $joinTable->getName()) {
765 18
                $joinTableName = $this->namingStrategy->joinTableName(
766 18
                    $property->getSourceEntity(),
767 18
                    $property->getTargetEntity(),
768 18
                    $property->getName()
769
                );
770
771 18
                $joinTable->setName($joinTableName);
772
            }
773
774 97
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() === $property->getTargetEntity() && ! $joinTable->hasColumns();
775
776 97
            if (! $joinTable->getJoinColumns()) {
777 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
778 16
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
779 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
780 16
                $joinColumn           = new JoinColumnMetadata();
781
782 16
                $joinColumn->setColumnName($columnName);
783 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
784 16
                $joinColumn->setOnDelete('CASCADE');
785
786 16
                $joinTable->addJoinColumn($joinColumn);
787
            }
788
789 97
            if (! $joinTable->getInverseJoinColumns()) {
790 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
791 16
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
792 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
793 16
                $joinColumn           = new JoinColumnMetadata();
794
795 16
                $joinColumn->setColumnName($columnName);
796 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
797 16
                $joinColumn->setOnDelete('CASCADE');
798
799 16
                $joinTable->addInverseJoinColumn($joinColumn);
800
            }
801
802 97
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
803
                /** @var JoinColumnMetadata $joinColumn */
804 97
                if (! $joinColumn->getReferencedColumnName()) {
805 2
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
806
                }
807
808 97
                $referencedColumnName = $joinColumn->getReferencedColumnName();
809
810 97
                if (! $joinColumn->getColumnName()) {
811 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
812 2
                        $property->getSourceEntity(),
813 2
                        $referencedColumnName
814
                    );
815
816 2
                    $joinColumn->setColumnName($columnName);
817
                }
818
            }
819
820 97
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
821
                /** @var JoinColumnMetadata $inverseJoinColumn */
822 97
                if (! $inverseJoinColumn->getReferencedColumnName()) {
823 2
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
824
                }
825
826 97
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
827
828 97
                if (! $inverseJoinColumn->getColumnName()) {
829 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
830 2
                        $property->getTargetEntity(),
831 2
                        $referencedColumnName
832
                    );
833
834 2
                    $inverseJoinColumn->setColumnName($columnName);
835
                }
836
            }
837
        }
838 110
    }
839
840
    /**
841
     * {@inheritDoc}
842
     */
843 401
    public function getIdentifierFieldNames()
844
    {
845 401
        return $this->identifier;
846
    }
847
848
    /**
849
     * Gets the name of the single id field. Note that this only works on
850
     * entity classes that have a single-field pk.
851
     *
852
     * @return string
853
     *
854
     * @throws MappingException If the class has a composite primary key.
855
     */
856 151
    public function getSingleIdentifierFieldName()
857
    {
858 151
        if ($this->isIdentifierComposite()) {
859 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
860
        }
861
862 150
        if (! isset($this->identifier[0])) {
863 1
            throw MappingException::noIdDefined($this->className);
864
        }
865
866 149
        return $this->identifier[0];
867
    }
868
869
    /**
870
     * INTERNAL:
871
     * Sets the mapped identifier/primary key fields of this class.
872
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
873
     *
874
     * @param mixed[] $identifier
875
     */
876 100
    public function setIdentifier(array $identifier)
877
    {
878 100
        $this->identifier = $identifier;
879 100
    }
880
881
    /**
882
     * {@inheritDoc}
883
     */
884 1053
    public function getIdentifier()
885
    {
886 1053
        return $this->identifier;
887
    }
888
889
    /**
890
     * {@inheritDoc}
891
     */
892 189
    public function hasField($fieldName)
893
    {
894 189
        return isset($this->declaredProperties[$fieldName])
895 189
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
896
    }
897
898
    /**
899
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
900
     *
901
     * @return ColumnMetadata[]
902
     */
903 441
    public function getIdentifierColumns(EntityManagerInterface $em) : array
904
    {
905 441
        $columns = [];
906
907 441
        foreach ($this->identifier as $idProperty) {
908 441
            $property = $this->getProperty($idProperty);
909
910 441
            if ($property instanceof FieldMetadata) {
911 435
                $columns[$property->getColumnName()] = $property;
912
913 435
                continue;
914
            }
915
916
            /** @var AssociationMetadata $property */
917
918
            // Association defined as Id field
919 25
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
920
921 25
            if (! $property->isOwningSide()) {
922
                $property    = $targetClass->getProperty($property->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

922
                /** @scrutinizer ignore-call */ 
923
                $property    = $targetClass->getProperty($property->getMappedBy());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
923
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
924
            }
925
926 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
927
                ? $property->getJoinTable()->getInverseJoinColumns()
928 25
                : $property->getJoinColumns();
929
930 25
            foreach ($joinColumns as $joinColumn) {
931
                /** @var JoinColumnMetadata $joinColumn */
932 25
                $columnName           = $joinColumn->getColumnName();
933 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
934
935 25
                if (! $joinColumn->getType()) {
936 13
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
937
                }
938
939 25
                $columns[$columnName] = $joinColumn;
940
            }
941
        }
942
943 441
        return $columns;
944
    }
945
946
    /**
947
     * Gets the name of the primary table.
948
     */
949 1575
    public function getTableName() : ?string
950
    {
951 1575
        return $this->table->getName();
952
    }
953
954
    /**
955
     * Gets primary table's schema name.
956
     */
957 14
    public function getSchemaName() : ?string
958
    {
959 14
        return $this->table->getSchema();
960
    }
961
962
    /**
963
     * Gets the table name to use for temporary identifier tables of this class.
964
     */
965 7
    public function getTemporaryIdTableName() : string
966
    {
967 7
        $schema = $this->getSchemaName() === null
968 6
            ? ''
969 7
            : $this->getSchemaName() . '_';
970
971
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
972 7
        return $schema . $this->getTableName() . '_id_tmp';
973
    }
974
975
    /**
976
     * Sets the mapped subclasses of this class.
977
     *
978
     * @param string[] $subclasses The names of all mapped subclasses.
979
     *
980
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
981
     */
982 4
    public function setSubclasses(array $subclasses) : void
983
    {
984 4
        foreach ($subclasses as $subclass) {
985 3
            $this->subClasses[] = $subclass;
986
        }
987 4
    }
988
989
    /**
990
     * @return string[]
991
     */
992 1080
    public function getSubClasses() : array
993
    {
994 1080
        return $this->subClasses;
995
    }
996
997
    /**
998
     * Sets the inheritance type used by the class and its subclasses.
999
     *
1000
     * @param int $type
1001
     *
1002
     * @throws MappingException
1003
     */
1004 120
    public function setInheritanceType($type) : void
1005
    {
1006 120
        if (! $this->isInheritanceType($type)) {
1007
            throw MappingException::invalidInheritanceType($this->className, $type);
1008
        }
1009
1010 120
        $this->inheritanceType = $type;
1011 120
    }
1012
1013
    /**
1014
     * Sets the override property mapping for an entity relationship.
1015
     *
1016
     * @throws RuntimeException
1017
     * @throws MappingException
1018
     * @throws CacheException
1019
     */
1020 12
    public function setPropertyOverride(Property $property) : void
1021
    {
1022 12
        $fieldName = $property->getName();
1023
1024 12
        if (! isset($this->declaredProperties[$fieldName])) {
1025 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1026
        }
1027
1028 10
        $originalProperty          = $this->getProperty($fieldName);
1029 10
        $originalPropertyClassName = get_class($originalProperty);
1030
1031
        // If moving from transient to persistent, assume it's a new property
1032 10
        if ($originalPropertyClassName === TransientMetadata::class) {
1033 1
            unset($this->declaredProperties[$fieldName]);
1034
1035 1
            $this->addProperty($property);
1036
1037 1
            return;
1038
        }
1039
1040
        // Do not allow to change property type
1041 9
        if ($originalPropertyClassName !== get_class($property)) {
1042
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1043
        }
1044
1045
        // Do not allow to change version property
1046 9
        if ($originalProperty instanceof FieldMetadata && $originalProperty->isVersioned()) {
1047
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1048
        }
1049
1050 9
        unset($this->declaredProperties[$fieldName]);
1051
1052 9
        if ($property instanceof FieldMetadata) {
1053
            // Unset defined fieldName prior to override
1054 5
            unset($this->fieldNames[$originalProperty->getColumnName()]);
0 ignored issues
show
Bug introduced by
The method getColumnName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

1054
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
1055
1056
            // Revert what should not be allowed to change
1057 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1058 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1059 9
        } elseif ($property instanceof AssociationMetadata) {
1060
            // Unset all defined fieldNames prior to override
1061 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1062 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1063 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1064
                }
1065
            }
1066
1067
            // Override what it should be allowed to change
1068 9
            if ($property->getInversedBy()) {
1069 2
                $originalProperty->setInversedBy($property->getInversedBy());
0 ignored issues
show
Bug introduced by
The method setInversedBy() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

1069
                $originalProperty->/** @scrutinizer ignore-call */ 
1070
                                   setInversedBy($property->getInversedBy());
Loading history...
1070
            }
1071
1072 9
            if ($property->getFetchMode() !== $originalProperty->getFetchMode()) {
0 ignored issues
show
Bug introduced by
The method getFetchMode() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

1072
            if ($property->getFetchMode() !== $originalProperty->/** @scrutinizer ignore-call */ getFetchMode()) {
Loading history...
1073 2
                $originalProperty->setFetchMode($property->getFetchMode());
0 ignored issues
show
Bug introduced by
The method setFetchMode() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

1073
                $originalProperty->/** @scrutinizer ignore-call */ 
1074
                                   setFetchMode($property->getFetchMode());
Loading history...
1074
            }
1075
1076 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) {
0 ignored issues
show
Bug introduced by
The method getJoinColumns() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

1076
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinColumns()) {
Loading history...
1077 5
                $originalProperty->setJoinColumns($property->getJoinColumns());
1078 8
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) {
0 ignored issues
show
Bug introduced by
The method getJoinTable() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

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

1078
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
1079 4
                $originalProperty->setJoinTable($property->getJoinTable());
1080
            }
1081
1082 9
            $property = $originalProperty;
1083
        }
1084
1085 9
        $this->addProperty($property);
0 ignored issues
show
Bug introduced by
It seems like $property can also be of type null; however, parameter $property of Doctrine\ORM\Mapping\ClassMetadata::addProperty() does only seem to accept Doctrine\ORM\Mapping\Property, maybe add an additional type check? ( Ignorable by Annotation )

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

1085
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
1086 9
    }
1087
1088
    /**
1089
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1090
     *
1091
     * @return bool
1092
     */
1093 336
    public function isRootEntity()
1094
    {
1095 336
        return $this->className === $this->getRootClassName();
1096
    }
1097
1098
    /**
1099
     * Checks whether a mapped field is inherited from a superclass.
1100
     *
1101
     * @param string $fieldName
1102
     *
1103
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1104
     */
1105 622
    public function isInheritedProperty($fieldName)
1106
    {
1107 622
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1108
1109 622
        return $declaringClass->className !== $this->className;
1110
    }
1111
1112
    /**
1113
     * {@inheritdoc}
1114
     */
1115 447
    public function setTable(TableMetadata $table) : void
1116
    {
1117 447
        $this->table = $table;
1118
1119 447
        if (empty($table->getName())) {
1120
            $table->setName($this->namingStrategy->classToTableName($this->className));
1121
        }
1122
1123
        // Make sure inherited and declared properties reflect newly defined table
1124 447
        foreach ($this->declaredProperties as $property) {
1125
            switch (true) {
1126 95
                case $property instanceof FieldMetadata:
1127 95
                    $property->setTableName($property->getTableName() ?? $table->getName());
1128 95
                    break;
1129
1130 42
                case $property instanceof ToOneAssociationMetadata:
1131
                    // Resolve association join column table names
1132 34
                    foreach ($property->getJoinColumns() as $joinColumn) {
1133
                        /** @var JoinColumnMetadata $joinColumn */
1134 34
                        $joinColumn->setTableName($joinColumn->getTableName() ?? $table->getName());
1135
                    }
1136
1137 34
                    break;
1138
            }
1139
        }
1140 447
    }
1141
1142
    /**
1143
     * Checks whether the given type identifies an inheritance type.
1144
     *
1145
     * @param int $type
1146
     *
1147
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1148
     */
1149 120
    private function isInheritanceType($type)
1150
    {
1151 120
        return $type === InheritanceType::NONE
1152 93
            || $type === InheritanceType::SINGLE_TABLE
1153 51
            || $type === InheritanceType::JOINED
1154 120
            || $type === InheritanceType::TABLE_PER_CLASS;
1155
    }
1156
1157 916
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1158
    {
1159 916
        foreach ($this->declaredProperties as $property) {
1160 916
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1161 916
                return $property;
1162
            }
1163
        }
1164
1165
        return null;
1166
    }
1167
1168
    /**
1169
     * Add a property mapping.
1170
     *
1171
     * @throws RuntimeException
1172
     * @throws MappingException
1173
     * @throws CacheException
1174
     * @throws ReflectionException
1175
     */
1176 436
    public function addProperty(Property $property)
1177
    {
1178 436
        $fieldName = $property->getName();
1179
1180
        // Check for empty field name
1181 436
        if (empty($fieldName)) {
1182 1
            throw MappingException::missingFieldName($this->className);
1183
        }
1184
1185 435
        $property->setDeclaringClass($this);
1186
1187
        switch (true) {
1188 435
            case $property instanceof FieldMetadata:
1189 410
                $property->setColumnName($property->getColumnName() ?? $property->getName());
1190
1191 410
                $this->fieldNames[$property->getColumnName()] = $property->getName();
1192
1193 410
                if ($property->isVersioned()) {
1194 21
                    $this->validateAndCompleteVersionFieldMapping($property);
1195
                }
1196
1197 409
                break;
1198
1199 292
            case $property instanceof OneToOneAssociationMetadata:
1200 129
                $this->validateAndCompleteAssociationMapping($property);
1201 128
                $this->validateAndCompleteToOneAssociationMetadata($property);
1202 127
                $this->validateAndCompleteOneToOneMapping($property);
1203 127
                break;
1204
1205 229
            case $property instanceof OneToManyAssociationMetadata:
1206 118
                $this->validateAndCompleteAssociationMapping($property);
1207 118
                $this->validateAndCompleteToManyAssociationMetadata($property);
1208 118
                $this->validateAndCompleteOneToManyMapping($property);
1209 118
                break;
1210
1211 224
            case $property instanceof ManyToOneAssociationMetadata:
1212 152
                $this->validateAndCompleteAssociationMapping($property);
1213 149
                $this->validateAndCompleteToOneAssociationMetadata($property);
1214 149
                $this->validateAndCompleteManyToOneMapping($property);
1215 149
                break;
1216
1217 126
            case $property instanceof ManyToManyAssociationMetadata:
1218 111
                $this->validateAndCompleteAssociationMapping($property);
1219 110
                $this->validateAndCompleteToManyAssociationMetadata($property);
1220 110
                $this->validateAndCompleteManyToManyMapping($property);
1221 110
                break;
1222
1223
            default:
1224
                // Transient properties are ignored on purpose here! =)
1225 29
                break;
1226
        }
1227
1228 428
        if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) {
1229 396
            $this->identifier[] = $fieldName;
1230
        }
1231
1232 428
        $this->addDeclaredProperty($property);
1233 428
    }
1234
1235
    /**
1236
     * INTERNAL:
1237
     * Adds a property mapping without completing/validating it.
1238
     * This is mainly used to add inherited property mappings to derived classes.
1239
     */
1240 96
    public function addInheritedProperty(Property $property)
1241
    {
1242 96
        if (isset($this->declaredProperties[$property->getName()])) {
1243 1
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
0 ignored issues
show
Bug introduced by
It seems like $this->getProperty($property->getName()) can also be of type null; however, parameter $property of Doctrine\ORM\Mapping\Map...on::duplicateProperty() does only seem to accept Doctrine\ORM\Mapping\Property, maybe add an additional type check? ( Ignorable by Annotation )

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

1243
            throw MappingException::duplicateProperty($this->className, /** @scrutinizer ignore-type */ $this->getProperty($property->getName()));
Loading history...
1244
        }
1245
1246 96
        $declaringClass    = $property->getDeclaringClass();
1247 96
        $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property;
1248
1249 96
        if ($inheritedProperty instanceof FieldMetadata) {
1250 95
            if (! $declaringClass->isMappedSuperclass) {
1251 73
                $inheritedProperty->setTableName($property->getTableName());
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

1251
                $inheritedProperty->setTableName($property->/** @scrutinizer ignore-call */ getTableName());
Loading history...
1252
            }
1253
1254 95
            if ($inheritedProperty->isVersioned()) {
1255 4
                $this->versionProperty = $inheritedProperty;
1256
            }
1257
1258 95
            $this->fieldNames[$property->getColumnName()] = $property->getName();
1259 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
1260 42
            if ($declaringClass->isMappedSuperclass) {
1261 10
                $inheritedProperty->setSourceEntity($this->className);
1262
            }
1263
1264
            // Need to add inherited fieldNames
1265 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1266 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1267
                    /** @var JoinColumnMetadata $joinColumn */
1268 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1269
                }
1270
            }
1271
        }
1272
1273 96
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1274 96
    }
1275
1276
    /**
1277
     * Registers a custom repository class for the entity class.
1278
     *
1279
     * @param string|null $repositoryClassName The class name of the custom mapper.
1280
     */
1281 30
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1282
    {
1283 30
        $this->customRepositoryClassName = $repositoryClassName;
1284 30
    }
1285
1286 163
    public function getCustomRepositoryClassName() : ?string
1287
    {
1288 163
        return $this->customRepositoryClassName;
1289
    }
1290
1291
    /**
1292
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1293
     *
1294
     * @param string $lifecycleEvent
1295
     *
1296
     * @return bool
1297
     */
1298
    public function hasLifecycleCallbacks($lifecycleEvent)
1299
    {
1300
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1301
    }
1302
1303
    /**
1304
     * Gets the registered lifecycle callbacks for an event.
1305
     *
1306
     * @param string $event
1307
     *
1308
     * @return string[]
1309
     */
1310
    public function getLifecycleCallbacks($event) : array
1311
    {
1312
        return $this->lifecycleCallbacks[$event] ?? [];
1313
    }
1314
1315
    /**
1316
     * Adds a lifecycle callback for entities of this class.
1317
     */
1318 16
    public function addLifecycleCallback(string $eventName, string $methodName)
1319
    {
1320 16
        if (in_array($methodName, $this->lifecycleCallbacks[$eventName] ?? [], true)) {
1321 3
            return;
1322
        }
1323
1324 16
        $this->lifecycleCallbacks[$eventName][] = $methodName;
1325 16
    }
1326
1327
    /**
1328
     * Adds a entity listener for entities of this class.
1329
     *
1330
     * @param string $eventName The entity lifecycle event.
1331
     * @param string $class     The listener class.
1332
     * @param string $method    The listener callback method.
1333
     *
1334
     * @throws MappingException
1335
     */
1336 13
    public function addEntityListener(string $eventName, string $class, string $methodName) : void
1337
    {
1338
        $listener = [
1339 13
            'class'  => $class,
1340 13
            'method' => $methodName,
1341
        ];
1342
1343 13
        if (! class_exists($class)) {
1344 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1345
        }
1346
1347 12
        if (! method_exists($class, $methodName)) {
1348 1
            throw MappingException::entityListenerMethodNotFound($class, $methodName, $this->className);
1349
        }
1350
1351
        // Check if entity listener already got registered and ignore it if positive
1352 11
        if (in_array($listener, $this->entityListeners[$eventName] ?? [], true)) {
1353 5
            return;
1354
        }
1355
1356 11
        $this->entityListeners[$eventName][] = $listener;
1357 11
    }
1358
1359
    /**
1360
     * Sets the discriminator column definition.
1361
     *
1362
     * @see getDiscriminatorColumn()
1363
     *
1364
     * @throws MappingException
1365
     */
1366 94
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1367
    {
1368 94
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1369
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1370
        }
1371
1372 94
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1373
1374 94
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1375
1376 94
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1377
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1378
        }
1379
1380 94
        $this->discriminatorColumn = $discriminatorColumn;
1381 94
    }
1382
1383
    /**
1384
     * Sets the discriminator values used by this class.
1385
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1386
     *
1387
     * @param string[] $map
1388
     *
1389
     * @throws MappingException
1390
     */
1391 89
    public function setDiscriminatorMap(array $map) : void
1392
    {
1393 89
        foreach ($map as $value => $className) {
1394 89
            $this->addDiscriminatorMapClass($value, $className);
1395
        }
1396 89
    }
1397
1398
    /**
1399
     * Adds one entry of the discriminator map with a new class and corresponding name.
1400
     *
1401
     * @param string|int $name
1402
     *
1403
     * @throws MappingException
1404
     */
1405 89
    public function addDiscriminatorMapClass($name, string $className) : void
1406
    {
1407 89
        $this->discriminatorMap[$name] = $className;
1408
1409 89
        if ($this->className === $className) {
1410 75
            $this->discriminatorValue = $name;
1411
1412 75
            return;
1413
        }
1414
1415 88
        if (! (class_exists($className) || interface_exists($className))) {
1416
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1417
        }
1418
1419 88
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1420 83
            $this->subClasses[] = $className;
1421
        }
1422 88
    }
1423
1424 1031
    public function getValueGenerationPlan() : ValueGenerationPlan
1425
    {
1426 1031
        return $this->valueGenerationPlan;
1427
    }
1428
1429 369
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1430
    {
1431 369
        $this->valueGenerationPlan = $valueGenerationPlan;
1432 369
    }
1433
1434 399
    public function checkPropertyDuplication(string $columnName) : bool
1435
    {
1436 399
        return isset($this->fieldNames[$columnName])
1437 399
            || ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName);
1438
    }
1439
1440
    /**
1441
     * Marks this class as read only, no change tracking is applied to it.
1442
     */
1443 2
    public function asReadOnly() : void
1444
    {
1445 2
        $this->readOnly = true;
1446 2
    }
1447
1448
    /**
1449
     * Whether this class is read only or not.
1450
     */
1451 446
    public function isReadOnly() : bool
1452
    {
1453 446
        return $this->readOnly;
1454
    }
1455
1456 1092
    public function isVersioned() : bool
1457
    {
1458 1092
        return $this->versionProperty !== null;
1459
    }
1460
1461
    /**
1462
     * Map Embedded Class
1463
     *
1464
     * @param mixed[] $mapping
1465
     *
1466
     * @throws MappingException
1467
     */
1468
    public function mapEmbedded(array $mapping) : void
0 ignored issues
show
Unused Code introduced by
The parameter $mapping is not used and could be removed. ( Ignorable by Annotation )

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

1468
    public function mapEmbedded(/** @scrutinizer ignore-unused */ array $mapping) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1469
    {
1470
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
1471
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1472
        }
1473
1474
        $this->embeddedClasses[$mapping['fieldName']] = [
1475
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1476
            'columnPrefix'   => $mapping['columnPrefix'],
1477
            'declaredField'  => $mapping['declaredField'] ?? null,
1478
            'originalField'  => $mapping['originalField'] ?? null,
1479
            'declaringClass' => $this,
1480
        ];*/
1481
    }
1482
1483
    /**
1484
     * Inline the embeddable class
1485
     *
1486
     * @param string $property
1487
     */
1488
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
0 ignored issues
show
Unused Code introduced by
The parameter $embeddable is not used and could be removed. ( Ignorable by Annotation )

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

1488
    public function inlineEmbeddable($property, /** @scrutinizer ignore-unused */ ClassMetadata $embeddable) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

1488
    public function inlineEmbeddable(/** @scrutinizer ignore-unused */ $property, ClassMetadata $embeddable) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1489
    {
1490
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1491
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1492
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1493
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1494
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1495
                ? $property . '.' . $fieldMapping['declaredField']
1496
                : $property;
1497
1498
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1499
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1500
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1501
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1502
                    $property,
1503
                    $fieldMapping['columnName'],
1504
                    $this->reflectionClass->getName(),
1505
                    $embeddable->reflectionClass->getName()
1506
                );
1507
            }
1508
1509
            $this->mapField($fieldMapping);
1510
        }*/
1511
    }
1512
}
1513