Passed
Pull Request — master (#7113)
by Michael
14:58 queued 03:10
created

ClassMetadata::getColumn()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4.128

Importance

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

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

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

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

919
                /** @scrutinizer ignore-call */ 
920
                $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...
920
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
921
            }
922
923 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
924
                ? $property->getJoinTable()->getInverseJoinColumns()
925 25
                : $property->getJoinColumns()
926
            ;
927
928 25
            foreach ($joinColumns as $joinColumn) {
929
                /** @var JoinColumnMetadata $joinColumn */
930 25
                $columnName           = $joinColumn->getColumnName();
931 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
932
933 25
                if (! $joinColumn->getType()) {
934 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

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

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

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

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

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

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

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

1084
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
1085 9
    }
1086
1087
    /**
1088
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1089
     *
1090
     * @return bool
1091
     */
1092 337
    public function isRootEntity()
1093
    {
1094 337
        return $this->className === $this->getRootClassName();
1095
    }
1096
1097
    /**
1098
     * Checks whether a mapped field is inherited from a superclass.
1099
     *
1100
     * @param string $fieldName
1101
     *
1102
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1103
     */
1104 621
    public function isInheritedProperty($fieldName)
1105
    {
1106 621
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1107
1108 621
        return ! ($declaringClass->className === $this->className);
1109
    }
1110
1111
    /**
1112
     * {@inheritdoc}
1113
     */
1114 441
    public function setTable(TableMetadata $table) : void
1115
    {
1116 441
        $this->table = $table;
1117
1118 441
        if (empty($table->getName())) {
1119
            $table->setName($this->namingStrategy->classToTableName($this->className));
1120
        }
1121 441
    }
1122
1123
    /**
1124
     * Checks whether the given type identifies an inheritance type.
1125
     *
1126
     * @param int $type
1127
     *
1128
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1129
     */
1130 118
    private function isInheritanceType($type)
1131
    {
1132 118
        return $type === InheritanceType::NONE
1133 92
            || $type === InheritanceType::SINGLE_TABLE
1134 50
            || $type === InheritanceType::JOINED
1135 118
            || $type === InheritanceType::TABLE_PER_CLASS;
1136
    }
1137
1138 909
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1139
    {
1140 909
        foreach ($this->declaredProperties as $property) {
1141 909
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1142 909
                return $property;
1143
            }
1144
        }
1145
1146
        return null;
1147
    }
1148
1149
    /**
1150
     * Add a property mapping.
1151
     *
1152
     * @throws \RuntimeException
1153
     * @throws MappingException
1154
     * @throws CacheException
1155
     */
1156 431
    public function addProperty(Property $property)
1157
    {
1158 431
        $fieldName = $property->getName();
1159
1160
        // Check for empty field name
1161 431
        if (empty($fieldName)) {
1162 1
            throw MappingException::missingFieldName($this->className);
1163
        }
1164
1165 430
        $property->setDeclaringClass($this);
1166
1167
        switch (true) {
1168 430
            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...
1169 20
                $this->validateAndCompleteFieldMapping($property);
1170 20
                $this->validateAndCompleteVersionFieldMapping($property);
1171 19
                break;
1172
1173 429
            case ($property instanceof FieldMetadata):
1174 406
                $this->validateAndCompleteFieldMapping($property);
1175 405
                break;
1176
1177 290
            case ($property instanceof OneToOneAssociationMetadata):
1178 131
                $this->validateAndCompleteAssociationMapping($property);
1179 130
                $this->validateAndCompleteToOneAssociationMetadata($property);
1180 129
                $this->validateAndCompleteOneToOneMapping($property);
1181 129
                break;
1182
1183 226
            case ($property instanceof OneToManyAssociationMetadata):
1184 116
                $this->validateAndCompleteAssociationMapping($property);
1185 116
                $this->validateAndCompleteToManyAssociationMetadata($property);
1186 116
                $this->validateAndCompleteOneToManyMapping($property);
1187 116
                break;
1188
1189 222
            case ($property instanceof ManyToOneAssociationMetadata):
1190 150
                $this->validateAndCompleteAssociationMapping($property);
1191 147
                $this->validateAndCompleteToOneAssociationMetadata($property);
1192 147
                $this->validateAndCompleteManyToOneMapping($property);
1193 147
                break;
1194
1195 126
            case ($property instanceof ManyToManyAssociationMetadata):
1196 110
                $this->validateAndCompleteAssociationMapping($property);
1197 109
                $this->validateAndCompleteToManyAssociationMetadata($property);
1198 109
                $this->validateAndCompleteManyToManyMapping($property);
1199 109
                break;
1200
1201
            default:
1202
                // Transient properties are ignored on purpose here! =)
1203 30
                break;
1204
        }
1205
1206 422
        $this->addDeclaredProperty($property);
1207 422
    }
1208
1209
    /**
1210
     * INTERNAL:
1211
     * Adds a property mapping without completing/validating it.
1212
     * This is mainly used to add inherited property mappings to derived classes.
1213
     */
1214 98
    public function addInheritedProperty(Property $property)
1215
    {
1216 98
        $inheritedProperty = clone $property;
1217 98
        $declaringClass    = $property->getDeclaringClass();
1218
1219 98
        if ($inheritedProperty instanceof FieldMetadata) {
1220 97
            if (! $declaringClass->isMappedSuperclass) {
1221 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

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

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

1469
    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...
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

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