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

ClassMetadata::getSqlResultSetMapping()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1467
    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

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