Failed Conditions
Pull Request — master (#6743)
by Grégoire
57:00
created

ClassMetadata::isRootEntity()   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 0
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 Doctrine\ORM\Cache\Exception\CacheException;
8
use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\Mapping\Factory\NamingStrategy;
11
use Doctrine\ORM\Reflection\ReflectionService;
12
use Doctrine\ORM\Sequencing\Planning\ValueGenerationPlan;
13
use Doctrine\ORM\Utility\PersisterHelper;
14
use function array_diff;
15
use function array_filter;
16
use function array_intersect;
17
use function array_map;
18
use function array_merge;
19
use function class_exists;
20
use function count;
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
29
/**
30
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
31
 * of an entity and its associations.
32
 *
33
 */
34
class ClassMetadata extends ComponentMetadata implements TableOwner
35
{
36
    /**
37
     * The name of the custom repository class used for the entity class.
38
     * (Optional).
39
     *
40
     * @var string
41
     */
42
    protected $customRepositoryClassName;
43
44
    /**
45
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
46
     *
47
     * @var bool
48
     */
49
    public $isMappedSuperclass = false;
50
51
    /**
52
     * READ-ONLY: Whether this class describes the mapping of an embeddable class.
53
     *
54
     * @var bool
55
     */
56
    public $isEmbeddedClass = false;
57
58
    /**
59
     * Whether this class describes the mapping of a read-only class.
60
     * That means it is never considered for change-tracking in the UnitOfWork.
61
     * It is a very helpful performance optimization for entities that are immutable,
62
     * either in your domain or through the relation database (coming from a view,
63
     * or a history table for example).
64
     *
65
     * @var bool
66
     */
67
    private $readOnly = false;
68
69
    /**
70
     * The names of all subclasses (descendants).
71
     *
72
     * @var string[]
73
     */
74
    protected $subClasses = [];
75
76
    /**
77
     * READ-ONLY: The names of all embedded classes based on properties.
78
     *
79
     * @var string[]
80
     */
81
    //public $embeddedClasses = [];
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
82
83
    /**
84
     * READ-ONLY: The registered lifecycle callbacks for entities of this class.
85
     *
86
     * @var string[][]
87
     */
88
    public $lifecycleCallbacks = [];
89
90
    /**
91
     * READ-ONLY: The registered entity listeners.
92
     *
93
     * @var mixed[][]
94
     */
95
    public $entityListeners = [];
96
97
    /**
98
     * READ-ONLY: The field names of all fields that are part of the identifier/primary key
99
     * of the mapped entity class.
100
     *
101
     * @var string[]
102
     */
103
    public $identifier = [];
104
105
    /**
106
     * READ-ONLY: The inheritance mapping type used by the class.
107
     *
108
     * @var string
109
     */
110
    public $inheritanceType = InheritanceType::NONE;
111
112
    /**
113
     * READ-ONLY: The policy used for change-tracking on entities of this class.
114
     *
115
     * @var string
116
     */
117
    public $changeTrackingPolicy = ChangeTrackingPolicy::DEFERRED_IMPLICIT;
118
119
    /**
120
     * READ-ONLY: The discriminator value of this class.
121
     *
122
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
123
     * where a discriminator column is used.</b>
124
     *
125
     * @var mixed
126
     *
127
     * @see discriminatorColumn
128
     */
129
    public $discriminatorValue;
130
131
    /**
132
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
133
     *
134
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
135
     * where a discriminator column is used.</b>
136
     *
137
     * @var string[]
138
     *
139
     * @see discriminatorColumn
140
     */
141
    public $discriminatorMap = [];
142
143
    /**
144
     * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
145
     * inheritance mappings.
146
     *
147
     * @var DiscriminatorColumnMetadata
148
     */
149
    public $discriminatorColumn;
150
151
    /**
152
     * READ-ONLY: The primary table metadata.
153
     *
154
     * @var TableMetadata
155
     */
156
    public $table;
157
158
    /**
159
     * READ-ONLY: An array of field names. Used to look up field names from column names.
160
     * Keys are column names and values are field names.
161
     *
162
     * @var string[]
163
     */
164
    public $fieldNames = [];
165
166
    /**
167
     * READ-ONLY: The field which is used for versioning in optimistic locking (if any).
168
     *
169
     * @var FieldMetadata|null
170
     */
171
    public $versionProperty;
172
173
    /**
174
     * NamingStrategy determining the default column and table names.
175
     *
176
     * @var NamingStrategy
177
     */
178
    protected $namingStrategy;
179
180
    /**
181
     * Value generation plan is responsible for generating values for auto-generated fields.
182
     *
183
     * @var ValueGenerationPlan
184
     */
185
    protected $valueGenerationPlan;
186
187
    /**
188
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
189
     * metadata of the class with the given name.
190
     *
191
     * @param string $entityName The name of the entity class.
192
     */
193 456
    public function __construct(
194
        string $entityName,
195
        ClassMetadataBuildingContext $metadataBuildingContext
196
    ) {
197 456
        parent::__construct($entityName, $metadataBuildingContext);
198
199 456
        $this->namingStrategy = $metadataBuildingContext->getNamingStrategy();
200 456
    }
201
202 2
    public function setClassName(string $className)
203
    {
204 2
        $this->className = $className;
205 2
    }
206
207
    public function getColumnsIterator() : \ArrayIterator
208
    {
209
        $iterator = parent::getColumnsIterator();
210
211
        if ($this->discriminatorColumn) {
212
            $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

212
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), /** @scrutinizer ignore-type */ $this->discriminatorColumn);
Loading history...
213
        }
214
215
        return $iterator;
216
    }
217
218 11
    public function getAncestorsIterator() : \ArrayIterator
219
    {
220 11
        $ancestors = new \ArrayIterator();
221 11
        $parent    = $this;
222
223 11
        while (($parent = $parent->parent) !== null) {
224 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
225 1
                continue;
226
            }
227
228 7
            $ancestors->append($parent);
229
        }
230
231 11
        return $ancestors;
232
    }
233
234 1252
    public function getRootClassName() : string
235
    {
236 1252
        return ($this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass)
237 402
            ? $this->parent->getRootClassName()
238 1252
            : $this->className
239
        ;
240
    }
241
242
    /**
243
     * Handles metadata cloning nicely.
244
     */
245 13
    public function __clone()
246
    {
247 13
        if ($this->cache) {
248 12
            $this->cache = clone $this->cache;
249
        }
250
251 13
        foreach ($this->declaredProperties as $name => $property) {
252 13
            $this->declaredProperties[$name] = clone $property;
253
        }
254 13
    }
255
256
    /**
257
     * Creates a string representation of this instance.
258
     *
259
     * @return string The string representation of this instance.
260
     *
261
     * @todo Construct meaningful string representation.
262
     */
263
    public function __toString()
264
    {
265
        return __CLASS__ . '@' . spl_object_id($this);
266
    }
267
268
    /**
269
     * Determines which fields get serialized.
270
     *
271
     * It is only serialized what is necessary for best unserialization performance.
272
     * That means any metadata properties that are not set or empty or simply have
273
     * their default value are NOT serialized.
274
     *
275
     * Parts that are also NOT serialized because they can not be properly unserialized:
276
     * - reflectionClass
277
     *
278
     * @return string[] The names of all the fields that should be serialized.
279
     */
280 5
    public function __sleep()
281
    {
282 5
        $serialized = [];
283
284
        // This metadata is always serialized/cached.
285 5
        $serialized = array_merge($serialized, [
286 5
            'declaredProperties',
287
            'fieldNames',
288
            //'embeddedClasses',
289
            'identifier',
290
            'className',
291
            'parent',
292
            'table',
293
            'valueGenerationPlan',
294
        ]);
295
296
        // The rest of the metadata is only serialized if necessary.
297 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
298
            $serialized[] = 'changeTrackingPolicy';
299
        }
300
301 5
        if ($this->customRepositoryClassName) {
302 1
            $serialized[] = 'customRepositoryClassName';
303
        }
304
305 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
306 1
            $serialized[] = 'inheritanceType';
307 1
            $serialized[] = 'discriminatorColumn';
308 1
            $serialized[] = 'discriminatorValue';
309 1
            $serialized[] = 'discriminatorMap';
310 1
            $serialized[] = 'subClasses';
311
        }
312
313 5
        if ($this->isMappedSuperclass) {
314
            $serialized[] = 'isMappedSuperclass';
315
        }
316
317 5
        if ($this->isEmbeddedClass) {
318
            $serialized[] = 'isEmbeddedClass';
319
        }
320
321 5
        if ($this->isVersioned()) {
322
            $serialized[] = 'versionProperty';
323
        }
324
325 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...
326
            $serialized[] = 'lifecycleCallbacks';
327
        }
328
329 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...
330 1
            $serialized[] = 'entityListeners';
331
        }
332
333 5
        if ($this->cache) {
334
            $serialized[] = 'cache';
335
        }
336
337 5
        if ($this->readOnly) {
338 1
            $serialized[] = 'readOnly';
339
        }
340
341 5
        return $serialized;
342
    }
343
344
    /**
345
     * Restores some state that can not be serialized/unserialized.
346
     */
347 1618
    public function wakeupReflection(ReflectionService $reflectionService) : void
348
    {
349
        // Restore ReflectionClass and properties
350 1618
        $this->reflectionClass = $reflectionService->getClass($this->className);
351
352 1618
        if (! $this->reflectionClass) {
353
            return;
354
        }
355
356 1618
        $this->className = $this->reflectionClass->getName();
357
358 1618
        foreach ($this->declaredProperties as $property) {
359
            /** @var Property $property */
360 1617
            $property->wakeupReflection($reflectionService);
361
        }
362 1618
    }
363
364
    /**
365
     * Validates Identifier.
366
     *
367
     * @throws MappingException
368
     */
369 362
    public function validateIdentifier() : void
370
    {
371 362
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
372 28
            return;
373
        }
374
375
        // Verify & complete identifier mapping
376 362
        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...
377 4
            throw MappingException::identifierRequired($this->className);
378
        }
379
380 358
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, function (Property $property) : bool {
381 358
            return $property instanceof FieldMetadata
382 358
                && $property->isPrimaryKey()
383 358
                && $property->hasValueGenerator();
384 358
        });
385
386 358
        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...
387
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
388
        }
389 358
    }
390
391
    /**
392
     * Validates association targets actually exist.
393
     *
394
     * @throws MappingException
395
     */
396 361
    public function validateAssociations() : void
397
    {
398 361
        array_map(
399 361
            function (Property $property) {
400 361
                if (! ($property instanceof AssociationMetadata)) {
401 358
                    return;
402
                }
403
404 249
                $targetEntity = $property->getTargetEntity();
405
406 249
                if (! class_exists($targetEntity)) {
407 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
408
                }
409 361
            },
410 361
            $this->declaredProperties
411
        );
412 360
    }
413
414
    /**
415
     * Validates lifecycle callbacks.
416
     *
417
     * @throws MappingException
418
     */
419 361
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
420
    {
421 361
        foreach ($this->lifecycleCallbacks as $callbacks) {
422
            /** @var array $callbacks */
423 11
            foreach ($callbacks as $callbackFuncName) {
424 11
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
425 11
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
426
                }
427
            }
428
        }
429 360
    }
430
431
    /**
432
     * Sets the change tracking policy used by this class.
433
     */
434 103
    public function setChangeTrackingPolicy(string $policy) : void
435
    {
436 103
        $this->changeTrackingPolicy = $policy;
437 103
    }
438
439
    /**
440
     * Checks whether a field is part of the identifier/primary key field(s).
441
     *
442
     * @param string $fieldName The field name.
443
     *
444
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
445
     */
446 1019
    public function isIdentifier(string $fieldName) : bool
447
    {
448 1019
        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...
449 1
            return false;
450
        }
451
452 1018
        if (! $this->isIdentifierComposite()) {
453 1014
            return $fieldName === $this->identifier[0];
454
        }
455
456 93
        return in_array($fieldName, $this->identifier, true);
457
    }
458
459 1202
    public function isIdentifierComposite() : bool
460
    {
461 1202
        return isset($this->identifier[1]);
462
    }
463
464
    /**
465
     * Validates & completes the basic mapping information for field mapping.
466
     *
467
     * @throws MappingException If something is wrong with the mapping.
468
     */
469 406
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
470
    {
471 406
        $fieldName  = $property->getName();
472 406
        $columnName = $property->getColumnName();
473
474 406
        if (empty($columnName)) {
475 342
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
476
477 342
            $property->setColumnName($columnName);
478
        }
479
480 406
        if (! $this->isMappedSuperclass) {
481 399
            $property->setTableName($this->getTableName());
482
        }
483
484
        // Check for already declared column
485 406
        if (isset($this->fieldNames[$columnName]) ||
486 406
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
487 2
            throw MappingException::duplicateColumnName($this->className, $columnName);
488
        }
489
490
        // Complete id mapping
491 405
        if ($property->isPrimaryKey()) {
492 390
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
493
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
494
            }
495
496 390
            if ($property->getType()->canRequireSQLConversion()) {
497
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($this->className, $property);
498
            }
499
500 390
            if (! in_array($fieldName, $this->identifier, true)) {
501 390
                $this->identifier[] = $fieldName;
502
            }
503
        }
504
505 405
        $this->fieldNames[$columnName] = $fieldName;
506 405
    }
507
508
    /**
509
     * Validates & completes the basic mapping information for field mapping.
510
     *
511
     * @throws MappingException If something is wrong with the mapping.
512
     */
513 20
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
514
    {
515 20
        $this->versionProperty = $property;
516
517 20
        $options = $property->getOptions();
518
519 20
        if (isset($options['default'])) {
520
            return;
521
        }
522
523 20
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'], true)) {
524 18
            $property->setOptions(array_merge($options, ['default' => 1]));
525
526 18
            return;
527
        }
528
529 3
        if ($property->getTypeName() === 'datetime') {
530 2
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
531
532 2
            return;
533
        }
534
535 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
536
    }
537
538
    /**
539
     * Validates & completes the basic mapping information that is common to all
540
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
541
     *
542
     * @throws MappingException If something is wrong with the mapping.
543
     * @throws CacheException   If entity is not cacheable.
544
     */
545 285
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
546
    {
547 285
        $fieldName    = $property->getName();
548 285
        $targetEntity = $property->getTargetEntity();
549
550 285
        if (! $targetEntity) {
551
            throw MappingException::missingTargetEntity($fieldName);
552
        }
553
554 285
        $property->setSourceEntity($this->className);
555 285
        $property->setTargetEntity($targetEntity);
556
557
        // Complete id mapping
558 285
        if ($property->isPrimaryKey()) {
559 45
            if ($property->isOrphanRemoval()) {
560 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
561
            }
562
563 44
            if (! in_array($property->getName(), $this->identifier, true)) {
564 44
                if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
565
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
566
                        $property->getTargetEntity(),
567
                        $this->className,
568
                        $fieldName
569
                    );
570
                }
571
572 44
                $this->identifier[] = $property->getName();
573
            }
574
575 44
            if ($this->cache && ! $property->getCache()) {
576
                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
584
        // Cascades
585 283
        $cascadeTypes = ['remove', 'persist', 'refresh'];
586 283
        $cascades     = array_map('strtolower', $property->getCascade());
587
588 283
        if (in_array('all', $cascades, true)) {
589 6
            $cascades = $cascadeTypes;
590
        }
591
592 283
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
593 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
594
595 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
596
        }
597
598 282
        $property->setCascade($cascades);
599 282
    }
600
601
    /**
602
     * Validates & completes a to-one association mapping.
603
     *
604
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
605
     *
606
     * @throws \RuntimeException
607
     * @throws MappingException
608
     */
609 247
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
610
    {
611 247
        $fieldName = $property->getName();
612
613 247
        if ($property->isOwningSide()) {
614 243
            if (empty($property->getJoinColumns())) {
615
                // Apply default join column
616 86
                $property->addJoinColumn(new JoinColumnMetadata());
617
            }
618
619 243
            $uniqueConstraintColumns = [];
620
621 243
            foreach ($property->getJoinColumns() as $joinColumn) {
622
                /** @var JoinColumnMetadata $joinColumn */
623 243
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
624 112
                    if (count($property->getJoinColumns()) === 1) {
625 110
                        if (! $property->isPrimaryKey()) {
626 110
                            $joinColumn->setUnique(true);
627
                        }
628
                    } else {
629 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
630
                    }
631
                }
632
633 243
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
634
635 243
                if (! $joinColumn->getColumnName()) {
636 99
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
637
                }
638
639 243
                if (! $joinColumn->getReferencedColumnName()) {
640 86
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
641
                }
642
643 243
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
644
            }
645
646 243
            if ($uniqueConstraintColumns) {
647 2
                if (! $this->table) {
648
                    throw new \RuntimeException(
649
                        'ClassMetadata::setTable() has to be called before defining a one to one relationship.'
650
                    );
651
                }
652
653 2
                $this->table->addUniqueConstraint(
654
                    [
655 2
                        'name'    => sprintf('%s_uniq', $fieldName),
656 2
                        'columns' => $uniqueConstraintColumns,
657
                        'options' => [],
658
                        'flags'   => [],
659
                    ]
660
                );
661
            }
662
        }
663
664 247
        if ($property->isOrphanRemoval()) {
665 7
            $cascades = $property->getCascade();
666
667 7
            if (! in_array('remove', $cascades, true)) {
668 6
                $cascades[] = 'remove';
669
670 6
                $property->setCascade($cascades);
671
            }
672
673
            // @todo guilhermeblanco where is this used?
674
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
675
            //$property->setUnique(false);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
676
        }
677
678 247
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
679 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
680
        }
681 246
    }
682
683
    /**
684
     * Validates & completes a to-many association mapping.
685
     *
686
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
687
     *
688
     * @throws MappingException
689
     */
690 172
    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

690
    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...
691
    {
692
        // Do nothing
693 172
    }
694
695
    /**
696
     * Validates & completes a one-to-one association mapping.
697
     *
698
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
699
     */
700 129
    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

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

918
                /** @scrutinizer ignore-call */ 
919
                $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...
919
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
920
            }
921
922 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
923
                ? $property->getJoinTable()->getInverseJoinColumns()
924 25
                : $property->getJoinColumns()
925
            ;
926
927 25
            foreach ($joinColumns as $joinColumn) {
928
                /** @var JoinColumnMetadata $joinColumn */
929 25
                $columnName           = $joinColumn->getColumnName();
930 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
931
932 25
                if (! $joinColumn->getType()) {
933 13
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

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

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

1052
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
1053
1054
            // Revert what should not be allowed to change
1055 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1056 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1057 9
        } elseif ($property instanceof AssociationMetadata) {
1058
            // Unset all defined fieldNames prior to override
1059 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1060 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1061 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1062
                }
1063
            }
1064
1065
            // Override what it should be allowed to change
1066 9
            if ($property->getInversedBy()) {
1067 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

1067
                $originalProperty->/** @scrutinizer ignore-call */ 
1068
                                   setInversedBy($property->getInversedBy());
Loading history...
1068
            }
1069
1070 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

1070
            if ($property->getFetchMode() !== $originalProperty->/** @scrutinizer ignore-call */ getFetchMode()) {
Loading history...
1071 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

1071
                $originalProperty->/** @scrutinizer ignore-call */ 
1072
                                   setFetchMode($property->getFetchMode());
Loading history...
1072
            }
1073
1074 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

1074
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinColumns()) {
Loading history...
1075 5
                $originalProperty->setJoinColumns($property->getJoinColumns());
1076 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

1076
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
1077 4
                $originalProperty->setJoinTable($property->getJoinTable());
1078
            }
1079
1080 9
            $property = $originalProperty;
1081
        }
1082
1083 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

1083
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
1084 9
    }
1085
1086
    /**
1087
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1088
     *
1089
     * @return bool
1090
     */
1091 337
    public function isRootEntity()
1092
    {
1093 337
        return $this->className === $this->getRootClassName();
1094
    }
1095
1096
    /**
1097
     * Checks whether a mapped field is inherited from a superclass.
1098
     *
1099
     * @param string $fieldName
1100
     *
1101
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1102
     */
1103 621
    public function isInheritedProperty($fieldName)
1104
    {
1105 621
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1106
1107 621
        return ! ($declaringClass->className === $this->className);
1108
    }
1109
1110
    /**
1111
     * {@inheritdoc}
1112
     */
1113 438
    public function setTable(TableMetadata $table) : void
1114
    {
1115 438
        $this->table = $table;
1116
1117 438
        if (empty($table->getName())) {
1118
            $table->setName($this->namingStrategy->classToTableName($this->className));
1119
        }
1120 438
    }
1121
1122
    /**
1123
     * Checks whether the given type identifies an inheritance type.
1124
     *
1125
     * @param int $type
1126
     *
1127
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1128
     */
1129 118
    private function isInheritanceType($type)
1130
    {
1131 118
        return $type === InheritanceType::NONE
1132 92
            || $type === InheritanceType::SINGLE_TABLE
1133 50
            || $type === InheritanceType::JOINED
1134 118
            || $type === InheritanceType::TABLE_PER_CLASS;
1135
    }
1136
1137 908
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1138
    {
1139 908
        foreach ($this->declaredProperties as $property) {
1140 908
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1141 908
                return $property;
1142
            }
1143
        }
1144
1145
        return null;
1146
    }
1147
1148
    /**
1149
     * Add a property mapping.
1150
     *
1151
     * @throws \RuntimeException
1152
     * @throws MappingException
1153
     * @throws CacheException
1154
     */
1155 428
    public function addProperty(Property $property)
1156
    {
1157 428
        $fieldName = $property->getName();
1158
1159
        // Check for empty field name
1160 428
        if (empty($fieldName)) {
1161 1
            throw MappingException::missingFieldName($this->className);
1162
        }
1163
1164 427
        $property->setDeclaringClass($this);
1165
1166
        switch (true) {
1167 427
            case ($property instanceof VersionFieldMetadata):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1168 20
                $this->validateAndCompleteFieldMapping($property);
1169 20
                $this->validateAndCompleteVersionFieldMapping($property);
1170 19
                break;
1171
1172 426
            case ($property instanceof FieldMetadata):
1173 405
                $this->validateAndCompleteFieldMapping($property);
1174 404
                break;
1175
1176 288
            case ($property instanceof OneToOneAssociationMetadata):
1177 131
                $this->validateAndCompleteAssociationMapping($property);
1178 130
                $this->validateAndCompleteToOneAssociationMetadata($property);
1179 129
                $this->validateAndCompleteOneToOneMapping($property);
1180 129
                break;
1181
1182 224
            case ($property instanceof OneToManyAssociationMetadata):
1183 116
                $this->validateAndCompleteAssociationMapping($property);
1184 116
                $this->validateAndCompleteToManyAssociationMetadata($property);
1185 116
                $this->validateAndCompleteOneToManyMapping($property);
1186 116
                break;
1187
1188 220
            case ($property instanceof ManyToOneAssociationMetadata):
1189 148
                $this->validateAndCompleteAssociationMapping($property);
1190 147
                $this->validateAndCompleteToOneAssociationMetadata($property);
1191 147
                $this->validateAndCompleteManyToOneMapping($property);
1192 147
                break;
1193
1194 126
            case ($property instanceof ManyToManyAssociationMetadata):
1195 110
                $this->validateAndCompleteAssociationMapping($property);
1196 109
                $this->validateAndCompleteToManyAssociationMetadata($property);
1197 109
                $this->validateAndCompleteManyToManyMapping($property);
1198 109
                break;
1199
1200
            default:
1201
                // Transient properties are ignored on purpose here! =)
1202 30
                break;
1203
        }
1204
1205 421
        $this->addDeclaredProperty($property);
1206 421
    }
1207
1208
    /**
1209
     * INTERNAL:
1210
     * Adds a property mapping without completing/validating it.
1211
     * This is mainly used to add inherited property mappings to derived classes.
1212
     */
1213 98
    public function addInheritedProperty(Property $property)
1214
    {
1215 98
        $inheritedProperty = clone $property;
1216 98
        $declaringClass    = $property->getDeclaringClass();
1217
1218 98
        if ($inheritedProperty instanceof FieldMetadata) {
1219 97
            if (! $declaringClass->isMappedSuperclass) {
1220 75
                $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

1220
                $inheritedProperty->setTableName($property->/** @scrutinizer ignore-call */ getTableName());
Loading history...
1221
            }
1222
1223 97
            $this->fieldNames[$property->getColumnName()] = $property->getName();
1224 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
1225 42
            if ($declaringClass->isMappedSuperclass) {
1226 10
                $inheritedProperty->setSourceEntity($this->className);
1227
            }
1228
1229
            // Need to add inherited fieldNames
1230 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1231 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1232
                    /** @var JoinColumnMetadata $joinColumn */
1233 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1234
                }
1235
            }
1236
        }
1237
1238 98
        if (isset($this->declaredProperties[$property->getName()])) {
1239 1
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
1240
        }
1241
1242 98
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1243
1244 98
        if ($inheritedProperty instanceof VersionFieldMetadata) {
1245 4
            $this->versionProperty = $inheritedProperty;
1246
        }
1247 98
    }
1248
1249
    /**
1250
     * Registers a custom repository class for the entity class.
1251
     *
1252
     * @param string|null $repositoryClassName The class name of the custom mapper.
1253
     */
1254 31
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1255
    {
1256 31
        $this->customRepositoryClassName = $repositoryClassName;
1257 31
    }
1258
1259 161
    public function getCustomRepositoryClassName() : ?string
1260
    {
1261 161
        return $this->customRepositoryClassName;
1262
    }
1263
1264
    /**
1265
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1266
     *
1267
     * @param string $lifecycleEvent
1268
     *
1269
     * @return bool
1270
     */
1271
    public function hasLifecycleCallbacks($lifecycleEvent)
1272
    {
1273
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1274
    }
1275
1276
    /**
1277
     * Gets the registered lifecycle callbacks for an event.
1278
     *
1279
     * @param string $event
1280
     *
1281
     * @return string[]
1282
     */
1283
    public function getLifecycleCallbacks($event)
1284
    {
1285
        return $this->lifecycleCallbacks[$event] ?? [];
1286
    }
1287
1288
    /**
1289
     * Adds a lifecycle callback for entities of this class.
1290
     *
1291
     * @param string $callback
1292
     * @param string $event
1293
     */
1294 17
    public function addLifecycleCallback($callback, $event)
1295
    {
1296 17
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) {
1297 3
            return;
1298
        }
1299
1300 17
        $this->lifecycleCallbacks[$event][] = $callback;
1301 17
    }
1302
1303
    /**
1304
     * Sets the lifecycle callbacks for entities of this class.
1305
     * Any previously registered callbacks are overwritten.
1306
     *
1307
     * @param string[][] $callbacks
1308
     */
1309 98
    public function setLifecycleCallbacks(array $callbacks) : void
1310
    {
1311 98
        $this->lifecycleCallbacks = $callbacks;
1312 98
    }
1313
1314
    /**
1315
     * Adds a entity listener for entities of this class.
1316
     *
1317
     * @param string $eventName The entity lifecycle event.
1318
     * @param string $class     The listener class.
1319
     * @param string $method    The listener callback method.
1320
     *
1321
     * @throws MappingException
1322
     */
1323 17
    public function addEntityListener(string $eventName, string $class, string $method) : void
1324
    {
1325
        $listener = [
1326 17
            'class'  => $class,
1327 17
            'method' => $method,
1328
        ];
1329
1330 17
        if (! class_exists($class)) {
1331 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1332
        }
1333
1334 16
        if (! method_exists($class, $method)) {
1335 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1336
        }
1337
1338 15
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
1339 1
            throw MappingException::duplicateEntityListener($class, $method, $this->className);
1340
        }
1341
1342 15
        $this->entityListeners[$eventName][] = $listener;
1343 15
    }
1344
1345
    /**
1346
     * Sets the discriminator column definition.
1347
     *
1348
     * @throws MappingException
1349
     *
1350
     * @see getDiscriminatorColumn()
1351
     */
1352 94
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1353
    {
1354 94
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1355 1
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1356
        }
1357
1358 93
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1359
1360 93
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1361
1362 93
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1363
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1364
        }
1365
1366 93
        $this->discriminatorColumn = $discriminatorColumn;
1367 93
    }
1368
1369
    /**
1370
     * Sets the discriminator values used by this class.
1371
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1372
     *
1373
     * @param string[] $map
1374
     *
1375
     * @throws MappingException
1376
     */
1377 91
    public function setDiscriminatorMap(array $map) : void
1378
    {
1379 91
        foreach ($map as $value => $className) {
1380 91
            $this->addDiscriminatorMapClass($value, $className);
1381
        }
1382 91
    }
1383
1384
    /**
1385
     * Adds one entry of the discriminator map with a new class and corresponding name.
1386
     *
1387
     * @param string|int $name
1388
     *
1389
     * @throws MappingException
1390
     */
1391 91
    public function addDiscriminatorMapClass($name, string $className) : void
1392
    {
1393 91
        $this->discriminatorMap[$name] = $className;
1394
1395 91
        if ($this->className === $className) {
1396 77
            $this->discriminatorValue = $name;
1397
1398 77
            return;
1399
        }
1400
1401 90
        if (! (class_exists($className) || interface_exists($className))) {
1402
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1403
        }
1404
1405 90
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1406 85
            $this->subClasses[] = $className;
1407
        }
1408 90
    }
1409
1410 1023
    public function getValueGenerationPlan() : ValueGenerationPlan
1411
    {
1412 1023
        return $this->valueGenerationPlan;
1413
    }
1414
1415 362
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1416
    {
1417 362
        $this->valueGenerationPlan = $valueGenerationPlan;
1418 362
    }
1419
1420
    /**
1421
     * Marks this class as read only, no change tracking is applied to it.
1422
     */
1423 2
    public function asReadOnly() : void
1424
    {
1425 2
        $this->readOnly = true;
1426 2
    }
1427
1428
    /**
1429
     * Whether this class is read only or not.
1430
     */
1431 444
    public function isReadOnly() : bool
1432
    {
1433 444
        return $this->readOnly;
1434
    }
1435
1436 1083
    public function isVersioned() : bool
1437
    {
1438 1083
        return $this->versionProperty !== null;
1439
    }
1440
1441
    /**
1442
     * Map Embedded Class
1443
     *
1444
     * @param mixed[] $mapping
1445
     *
1446
     * @throws MappingException
1447
     */
1448
    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

1448
    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...
1449
    {
1450
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1451
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1452
        }
1453
1454
        $this->embeddedClasses[$mapping['fieldName']] = [
1455
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1456
            'columnPrefix'   => $mapping['columnPrefix'],
1457
            'declaredField'  => $mapping['declaredField'] ?? null,
1458
            'originalField'  => $mapping['originalField'] ?? null,
1459
            'declaringClass' => $this,
1460
        ];*/
1461
    }
1462
1463
    /**
1464
     * Inline the embeddable class
1465
     *
1466
     * @param string $property
1467
     */
1468
    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

1468
    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

1468
    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...
1469
    {
1470
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1471
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1472
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1473
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1474
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1475
                ? $property . '.' . $fieldMapping['declaredField']
1476
                : $property;
1477
1478
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1479
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1480
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1481
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1482
                    $property,
1483
                    $fieldMapping['columnName'],
1484
                    $this->reflectionClass->getName(),
1485
                    $embeddable->reflectionClass->getName()
1486
                );
1487
            }
1488
1489
            $this->mapField($fieldMapping);
1490
        }*/
1491
    }
1492
}
1493