Failed Conditions
Pull Request — master (#7825)
by
unknown
65:47 queued 01:43
created

ClassMetadata::addInheritedProperty()   C

Complexity

Conditions 14
Paths 19

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 23.4216

Importance

Changes 0
Metric Value
cc 14
eloc 21
nc 19
nop 1
dl 0
loc 40
ccs 7
cts 11
cp 0.6364
crap 23.4216
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

206
        $this->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
207 98
        $this->setIdentifier($parent->identifier);
208 98
        $this->setChangeTrackingPolicy($parent->changeTrackingPolicy);
209
210 98
        if ($parent->discriminatorColumn) {
211 70
            $this->setDiscriminatorColumn($parent->discriminatorColumn);
212 70
            $this->setDiscriminatorMap($parent->discriminatorMap);
213
        }
214
215 98
        if ($parent->isMappedSuperclass) {
216 27
            $this->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
217
        }
218
219 98
        if ($parent->cache) {
220 3
            $this->setCache(clone $parent->cache);
221
        }
222
223 98
        if (! empty($parent->lifecycleCallbacks)) {
224 5
            $this->lifecycleCallbacks = $parent->lifecycleCallbacks;
225
        }
226
227 98
        if (! empty($parent->entityListeners)) {
228 7
            $this->entityListeners = $parent->entityListeners;
229
        }
230 98
    }
231
232
    public function setClassName(string $className)
233
    {
234
        $this->className = $className;
235
    }
236
237
    public function getColumnsIterator() : ArrayIterator
238
    {
239
        $iterator = parent::getColumnsIterator();
240
241
        if ($this->discriminatorColumn) {
242
            $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

242
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), /** @scrutinizer ignore-type */ $this->discriminatorColumn);
Loading history...
243
        }
244
245
        return $iterator;
246
    }
247
248 11
    public function getAncestorsIterator() : ArrayIterator
249
    {
250 11
        $ancestors = new ArrayIterator();
251 11
        $parent    = $this;
252
253 11
        while (($parent = $parent->parent) !== null) {
254 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
255 1
                continue;
256
            }
257
258 7
            $ancestors->append($parent);
259
        }
260
261 11
        return $ancestors;
262
    }
263
264 1260
    public function getRootClassName() : string
265
    {
266 1260
        $rootClass = $parentClass = $this;
267 402
        while (($parentClass = $parentClass->getParent()) !== null) {
268 1260
            if (! $parentClass->isMappedSuperclass) {
269
                $rootClass = $parentClass;
270
            }
271
        }
272
273
        return $rootClass->className;
274 13
    }
275
276 13
    /**
277 12
     * Handles metadata cloning nicely.
278
     */
279
    public function __clone()
280 13
    {
281 13
        if ($this->cache) {
282
            $this->cache = clone $this->cache;
283 13
        }
284
285
        foreach ($this->properties as $name => $property) {
286
            $this->properties[$name] = clone $property;
287
        }
288
    }
289
290
    /**
291
     * Creates a string representation of this instance.
292
     *
293
     * @return string The string representation of this instance.
294
     *
295
     * @todo Construct meaningful string representation.
296
     */
297
    public function __toString()
298
    {
299
        return self::class . '@' . spl_object_id($this);
300
    }
301
302
    /**
303
     * Determines which fields get serialized.
304
     *
305
     * It is only serialized what is necessary for best unserialization performance.
306
     * That means any metadata properties that are not set or empty or simply have
307
     * their default value are NOT serialized.
308
     *
309 4
     * Parts that are also NOT serialized because they can not be properly unserialized:
310
     * - reflectionClass
311 4
     *
312
     * @return string[] The names of all the fields that should be serialized.
313
     */
314 4
    public function __sleep()
315 4
    {
316
        $serialized = [];
317
318
        // This metadata is always serialized/cached.
319
        $serialized = array_merge($serialized, [
320
            'properties',
321
            'fieldNames',
322
            //'embeddedClasses',
323
            'identifier',
324
            'className',
325
            'parent',
326 4
            'table',
327
            'valueGenerationPlan',
328
        ]);
329
330 4
        // The rest of the metadata is only serialized if necessary.
331 1
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
332
            $serialized[] = 'changeTrackingPolicy';
333
        }
334 4
335 1
        if ($this->customRepositoryClassName) {
336 1
            $serialized[] = 'customRepositoryClassName';
337 1
        }
338 1
339 1
        if ($this->inheritanceType !== InheritanceType::NONE) {
340
            $serialized[] = 'inheritanceType';
341
            $serialized[] = 'discriminatorColumn';
342 4
            $serialized[] = 'discriminatorValue';
343
            $serialized[] = 'discriminatorMap';
344
            $serialized[] = 'subClasses';
345
        }
346 4
347
        if ($this->isMappedSuperclass) {
348
            $serialized[] = 'isMappedSuperclass';
349
        }
350 4
351
        if ($this->isEmbeddedClass) {
352
            $serialized[] = 'isEmbeddedClass';
353
        }
354 4
355
        if ($this->isVersioned()) {
356
            $serialized[] = 'versionProperty';
357
        }
358 4
359 1
        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...
360
            $serialized[] = 'lifecycleCallbacks';
361
        }
362 4
363
        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...
364
            $serialized[] = 'entityListeners';
365
        }
366 4
367 1
        if ($this->cache) {
368
            $serialized[] = 'cache';
369
        }
370 4
371
        if ($this->readOnly) {
372
            $serialized[] = 'readOnly';
373
        }
374
375
        return $serialized;
376 104
    }
377
378 104
    /**
379 104
     * Sets the change tracking policy used by this class.
380
     */
381
    public function setChangeTrackingPolicy(string $policy) : void
382
    {
383
        $this->changeTrackingPolicy = $policy;
384
    }
385
386
    /**
387
     * Checks whether a field is part of the identifier/primary key field(s).
388 1027
     *
389
     * @param string $fieldName The field name.
390 1027
     *
391 1
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
392
     */
393
    public function isIdentifier(string $fieldName) : bool
394 1026
    {
395 1022
        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...
396
            return false;
397
        }
398 93
399
        if (! $this->isIdentifierComposite()) {
400
            return $fieldName === $this->identifier[0];
401 1213
        }
402
403 1213
        return in_array($fieldName, $this->identifier, true);
404
    }
405
406
    public function isIdentifierComposite() : bool
407
    {
408
        return isset($this->identifier[1]);
409
    }
410
411 367
    /**
412
     * Validates Identifier.
413 367
     *
414 27
     * @throws MappingException
415
     */
416
    public function validateIdentifier() : void
417
    {
418 367
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
419 2
            return;
420
        }
421
422
        // Verify & complete identifier mapping
423 365
        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...
424 365
            throw MappingException::identifierRequired($this->className);
425 365
        }
426 365
427
        $explicitlyGeneratedProperties = array_filter($this->properties, static function (Property $property) : bool {
428 365
            return $property instanceof FieldMetadata
429
                && $property->isPrimaryKey()
430
                && $property->hasValueGenerator();
431 365
        });
432
433
        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...
434
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
435
        }
436
    }
437
438 368
    /**
439
     * Validates lifecycle callbacks.
440 368
     *
441
     * @throws MappingException
442 10
     */
443 10
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
444 1
    {
445
        foreach ($this->lifecycleCallbacks as $callbacks) {
446
            /** @var array $callbacks */
447
            foreach ($callbacks as $callbackFuncName) {
448 367
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
449
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
450
                }
451
            }
452
        }
453 401
    }
454
455 401
    /**
456
     * {@inheritDoc}
457
     */
458
    public function getIdentifierFieldNames()
459
    {
460
        return $this->identifier;
461
    }
462
463
    /**
464
     * Gets the name of the single id field. Note that this only works on
465
     * entity classes that have a single-field pk.
466 151
     *
467
     * @return string
468 151
     *
469 1
     * @throws MappingException If the class has a composite primary key.
470
     */
471
    public function getSingleIdentifierFieldName()
472 150
    {
473 1
        if ($this->isIdentifierComposite()) {
474
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
475
        }
476 149
477
        if (! isset($this->identifier[0])) {
478
            throw MappingException::noIdDefined($this->className);
479
        }
480
481
        return $this->identifier[0];
482
    }
483
484
    /**
485
     * INTERNAL:
486 100
     * Sets the mapped identifier/primary key fields of this class.
487
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
488 100
     *
489 100
     * @param mixed[] $identifier
490
     */
491
    public function setIdentifier(array $identifier)
492
    {
493
        $this->identifier = $identifier;
494 1053
    }
495
496 1053
    /**
497
     * {@inheritDoc}
498
     */
499
    public function getIdentifier()
500
    {
501
        return $this->identifier;
502 189
    }
503
504 189
    /**
505 189
     * {@inheritDoc}
506
     */
507
    public function hasField($fieldName)
508
    {
509
        return isset($this->properties[$fieldName])
510
            && $this->properties[$fieldName] instanceof FieldMetadata;
511
    }
512
513 441
    /**
514
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
515 441
     *
516
     * @return ColumnMetadata[]
517 441
     */
518 441
    public function getIdentifierColumns(EntityManagerInterface $em) : array
519
    {
520 441
        $columns = [];
521 435
522
        foreach ($this->identifier as $idProperty) {
523 435
            $property = $this->getProperty($idProperty);
524
525
            if ($property instanceof FieldMetadata) {
526
                $columns[$property->getColumnName()] = $property;
527
528
                continue;
529 25
            }
530
531 25
            /** @var AssociationMetadata $property */
532
533
            // Association defined as Id field
534
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
535
536 25
            if (! $property->isOwningSide()) {
537
                $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

537
                /** @scrutinizer ignore-call */ 
538
                $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...
538 25
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
539
            }
540 25
541
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
542 25
                ? $property->getJoinTable()->getInverseJoinColumns()
543 25
                : $property->getJoinColumns();
544
545 25
            foreach ($joinColumns as $joinColumn) {
546 13
                /** @var JoinColumnMetadata $joinColumn */
547
                $columnName           = $joinColumn->getColumnName();
548
                $referencedColumnName = $joinColumn->getReferencedColumnName();
549 25
550
                if (! $joinColumn->getType()) {
551
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
552
                }
553 441
554
                $columns[$columnName] = $joinColumn;
555
            }
556
        }
557
558
        return $columns;
559 1570
    }
560
561 1570
    /**
562
     * Gets the name of the primary table.
563
     */
564
    public function getTableName() : ?string
565
    {
566
        return $this->table->getName();
567 23
    }
568
569 23
    /**
570
     * Gets primary table's schema name.
571
     */
572
    public function getSchemaName() : ?string
573
    {
574
        return $this->table->getSchema();
575 7
    }
576
577 7
    /**
578 6
     * Gets the table name to use for temporary identifier tables of this class.
579 7
     */
580
    public function getTemporaryIdTableName() : string
581
    {
582 7
        $schema = $this->getSchemaName() === null
583
            ? ''
584
            : $this->getSchemaName() . '_';
585
586
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
587
        return $schema . $this->getTableName() . '_id_tmp';
588
    }
589
590
    /**
591
     * Sets the mapped subclasses of this class.
592 4
     *
593
     * @param string[] $subclasses The names of all mapped subclasses.
594 4
     *
595 3
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
596
     */
597 4
    public function setSubclasses(array $subclasses) : void
598
    {
599
        foreach ($subclasses as $subclass) {
600
            $this->subClasses[] = $subclass;
601
        }
602 1080
    }
603
604 1080
    /**
605
     * @return string[]
606
     */
607
    public function getSubClasses() : array
608
    {
609
        return $this->subClasses;
610
    }
611
612
    /**
613
     * Sets the inheritance type used by the class and its subclasses.
614 120
     *
615
     * @param int $type
616 120
     *
617
     * @throws MappingException
618
     */
619
    public function setInheritanceType($type) : void
620 120
    {
621 120
        if (! $this->isInheritanceType($type)) {
622
            throw MappingException::invalidInheritanceType($this->className, $type);
623
        }
624
625
        $this->inheritanceType = $type;
626
    }
627
628
    /**
629
     * Sets the override property mapping for an entity relationship.
630 12
     *
631
     * @throws RuntimeException
632 12
     * @throws MappingException
633
     * @throws CacheException
634 12
     */
635 2
    public function setPropertyOverride(Property $property) : void
636
    {
637
        $fieldName = $property->getName();
638 10
639 10
        if (! isset($this->properties[$fieldName])) {
640
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
641
        }
642 10
643 1
        $originalProperty          = $this->getProperty($fieldName);
644
        $originalPropertyClassName = get_class($originalProperty);
645 1
646
        // If moving from transient to persistent, assume it's a new property
647 1
        if ($originalPropertyClassName === TransientMetadata::class) {
648
            unset($this->properties[$fieldName]);
649
650
            $this->addProperty($property);
651 9
652
            return;
653
        }
654
655
        // Do not allow to change property type
656 9
        if ($originalPropertyClassName !== get_class($property)) {
657
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
658
        }
659
660 9
        // Do not allow to change version property
661
        if ($originalProperty instanceof FieldMetadata && $originalProperty->isVersioned()) {
662 9
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
663
        }
664 5
665
        unset($this->properties[$fieldName]);
666
667 5
        if ($property instanceof FieldMetadata) {
668 5
            // Unset defined fieldName prior to override
669 9
            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

669
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
670
671 9
            // Revert what should not be allowed to change
672 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
673 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
674
        } elseif ($property instanceof AssociationMetadata) {
675
            // Unset all defined fieldNames prior to override
676
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
677
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
678 9
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
679 8
                }
680
            }
681
682 9
            // Override what it should be allowed to change
683 2
            if ($property->getInversedBy()) {
684
                $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

684
                $originalProperty->/** @scrutinizer ignore-call */ 
685
                                   setInversedBy($property->getInversedBy());
Loading history...
685
            }
686 9
687 5
            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

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

688
                $originalProperty->/** @scrutinizer ignore-call */ 
689
                                   setFetchMode($property->getFetchMode());
Loading history...
689 8
            }
690
691
            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

691
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinColumns()) {
Loading history...
692 9
                $originalProperty->setJoinColumns($property->getJoinColumns());
693
            } 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

693
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
694
                $originalProperty->setJoinTable($property->getJoinTable());
695 9
            }
696 9
697
            $property = $originalProperty;
698
        }
699
700
        $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

700
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
701
    }
702
703 336
    /**
704
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
705 336
     *
706
     * @return bool
707
     */
708
    public function isRootEntity()
709
    {
710
        return $this->className === $this->getRootClassName();
711
    }
712
713
    /**
714
     * Checks whether a mapped field is inherited from a superclass.
715 622
     *
716
     * @param string $fieldName
717 622
     *
718
     * @return bool TRUE if the field is inherited, FALSE otherwise.
719 622
     */
720
    public function isInheritedProperty($fieldName)
721
    {
722
        $declaringClass = $this->properties[$fieldName]->getDeclaringClass();
723
724
        return $declaringClass->className !== $this->className;
725 430
    }
726
727 430
    /**
728
     * Checks whether a mapped column is inherited from a parent table.
729
     *
730 430
     * @param string $fieldName
731
     *
732 95
     * @return bool TRUE if the field is inherited, FALSE otherwise.
733 95
     */
734 95
    public function isInheritedColumn($fieldName)
735
    {
736 42
        $declaringClass = $this->getPropertyTableClass($fieldName);
737
738 34
        return $declaringClass->className !== $this->className;
739
    }
740 34
741
    /**
742
     * Gets the class for the table that a field belongs to.
743 34
     *
744
     * @param string $fieldName
745
     *
746 430
     * @return ClassMetadata.
0 ignored issues
show
Documentation Bug introduced by
The doc comment ClassMetadata. at position 0 could not be parsed: Unknown type name 'ClassMetadata.' at position 0 in ClassMetadata..
Loading history...
747
     */
748
    public function getPropertyTableClass($fieldName) : ClassMetadata
749
    {
750
        $declaringClass = $this->properties[$fieldName]->getDeclaringClass();
751
        if (! $declaringClass->isMappedSuperclass) {
752
            return $declaringClass;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $declaringClass returns the type Doctrine\ORM\Mapping\ComponentMetadata which includes types incompatible with the type-hinted return Doctrine\ORM\Mapping\ClassMetadata.
Loading history...
753
        }
754
755 120
        $tableClass = $parentClass = $this;
756
        while (($parentClass = $parentClass->getParent()) && $parentClass !== $declaringClass) {
757 120
            if (! $parentClass->isMappedSuperclass) {
758 93
                $tableClass = $parentClass;
759 51
            }
760 120
        }
761
762
        return $tableClass;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tableClass could return the type Doctrine\ORM\Mapping\ComponentMetadata which includes types incompatible with the type-hinted return Doctrine\ORM\Mapping\ClassMetadata. Consider adding an additional type-check to rule them out.
Loading history...
763 916
    }
764
765 916
    /**
766 916
     * {@inheritdoc}
767 916
     */
768
    public function setTable(TableMetadata $table) : void
769
    {
770
        $this->table = $table;
771
772
        // Make sure inherited and declared properties reflect newly defined table
773
        foreach ($this->properties as $property) {
774
            switch (true) {
775
                case $property instanceof FieldMetadata:
776
                    $property->setTableName($property->getTableName() ?? $table->getName());
777
                    break;
778
779
                case $property instanceof ToOneAssociationMetadata:
780
                    // Resolve association join column table names
781
                    foreach ($property->getJoinColumns() as $joinColumn) {
782 417
                        /** @var JoinColumnMetadata $joinColumn */
783
                        $joinColumn->setTableName($joinColumn->getTableName() ?? $table->getName());
784 417
                    }
785
786
                    break;
787 417
            }
788 1
        }
789
    }
790
791 416
    /**
792
     * Checks whether the given type identifies an inheritance type.
793
     *
794 416
     * @param int $type
795 406
     *
796 20
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
797
     */
798
    private function isInheritanceType($type)
799 406
    {
800 406
        return $type === InheritanceType::NONE
801
            || $type === InheritanceType::SINGLE_TABLE
802 275
            || $type === InheritanceType::JOINED
803 241
            || $type === InheritanceType::TABLE_PER_CLASS;
804 233
    }
805
806
    public function getColumn(string $columnName) : ?LocalColumnMetadata
807 241
    {
808
        foreach ($this->properties as $property) {
809
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
810
                return $property;
811 178
            }
812
        }
813
814 416
        return null;
815 395
    }
816
817
    /**
818 416
     * Add a property mapping.
819 416
     *
820
     * @throws RuntimeException
821
     * @throws MappingException
822
     * @throws CacheException
823
     * @throws ReflectionException
824
     */
825
    public function addProperty(Property $property) : void
826 96
    {
827
        $fieldName = $property->getName();
828 96
829 1
        // Check for empty field name
830
        if (empty($fieldName)) {
831
            throw MappingException::missingFieldName($this->className);
832 96
        }
833 96
834
        $property->setDeclaringClass($this);
835 96
836 95
        switch (true) {
837 73
            case $property instanceof FieldMetadata:
838
                if ($property->isVersioned()) {
839
                    $this->versionProperty = $property;
840 95
                }
841 4
842
                $this->fieldNames[$property->getColumnName()] = $property->getName();
843
                break;
844 95
845 43
            case $property instanceof ToOneAssociationMetadata:
846 42
                foreach ($property->getJoinColumns() as $joinColumnMetadata) {
847 10
                    $this->fieldNames[$joinColumnMetadata->getColumnName()] = $property->getName();
848
                }
849
850
                break;
851 42
852 35
            default:
853
                // Transient properties are ignored on purpose here! =)
854 34
                break;
855
        }
856
857
        if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) {
858
            $this->identifier[] = $fieldName;
859 96
        }
860 96
861
        parent::addProperty($property);
862
    }
863
864
    /**
865
     * INTERNAL:
866
     * Adds a property mapping without completing/validating it.
867 30
     * This is mainly used to add inherited property mappings to derived classes.
868
     */
869 30
    public function addInheritedProperty(Property $property)
870 30
    {
871
        if (isset($this->properties[$property->getName()])) {
872 163
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
0 ignored issues
show
Bug introduced by
It seems like $this->getProperty($property->getName()) can also be of type null; however, parameter $property of Doctrine\ORM\Mapping\Map...on::duplicateProperty() does only seem to accept Doctrine\ORM\Mapping\Property, maybe add an additional type check? ( Ignorable by Annotation )

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

872
            throw MappingException::duplicateProperty($this->className, /** @scrutinizer ignore-type */ $this->getProperty($property->getName()));
Loading history...
873
        }
874 163
875
        $declaringClass    = $property->getDeclaringClass();
876
        $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property;
877
878
        if ($inheritedProperty instanceof FieldMetadata) {
879
            if (! $declaringClass->isMappedSuperclass) {
880
                $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

880
                $inheritedProperty->setTableName($property->/** @scrutinizer ignore-call */ getTableName());
Loading history...
881
            }
882
883
            if ($inheritedProperty->isVersioned()) {
884
                $this->versionProperty = $inheritedProperty;
885
            }
886
887
            $this->fieldNames[$property->getColumnName()] = $property->getName();
888
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
889
            if ($declaringClass->isMappedSuperclass) {
890
                $tableClass = $parentClass = $this;
891
                while (($parentClass = $parentClass->getParent()) && $parentClass !== $declaringClass) {
892
                    if (! $parentClass->isMappedSuperclass) {
893
                        $tableClass = $parentClass;
894
                    }
895
                }
896
                $inheritedProperty->setSourceEntity($tableClass->className);
897
            }
898
899
            // Need to add inherited fieldNames
900
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
901
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
902
                    /** @var JoinColumnMetadata $joinColumn */
903
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
904 16
                }
905
            }
906 16
        }
907 3
908
        $this->properties[$property->getName()] = $inheritedProperty;
909
    }
910 16
911 16
    /**
912
     * Registers a custom repository class for the entity class.
913
     *
914
     * @param string|null $repositoryClassName The class name of the custom mapper.
915
     */
916
    public function setCustomRepositoryClassName(?string $repositoryClassName)
917
    {
918
        $this->customRepositoryClassName = $repositoryClassName;
919
    }
920
921
    public function getCustomRepositoryClassName() : ?string
922 13
    {
923
        return $this->customRepositoryClassName;
924
    }
925 13
926 13
    /**
927
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
928
     *
929 13
     * @param string $lifecycleEvent
930 1
     *
931
     * @return bool
932
     */
933 12
    public function hasLifecycleCallbacks($lifecycleEvent)
934 1
    {
935
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
936
    }
937
938 11
    /**
939 5
     * Gets the registered lifecycle callbacks for an event.
940
     *
941
     * @param string $event
942 11
     *
943 11
     * @return string[]
944
     */
945
    public function getLifecycleCallbacks($event) : array
946
    {
947
        return $this->lifecycleCallbacks[$event] ?? [];
948
    }
949
950
    /**
951
     * Adds a lifecycle callback for entities of this class.
952 94
     */
953
    public function addLifecycleCallback(string $eventName, string $methodName)
954 94
    {
955
        if (in_array($methodName, $this->lifecycleCallbacks[$eventName] ?? [], true)) {
956
            return;
957
        }
958 94
959
        $this->lifecycleCallbacks[$eventName][] = $methodName;
960 94
    }
961
962 94
    /**
963
     * Adds a entity listener for entities of this class.
964
     *
965
     * @param string $eventName The entity lifecycle event.
966 94
     * @param string $class     The listener class.
967 94
     * @param string $method    The listener callback method.
968
     *
969
     * @throws MappingException
970
     */
971
    public function addEntityListener(string $eventName, string $class, string $methodName) : void
972
    {
973
        $listener = [
974
            'class'  => $class,
975
            'method' => $methodName,
976
        ];
977 89
978
        if (! class_exists($class)) {
979 89
            throw MappingException::entityListenerClassNotFound($class, $this->className);
980 89
        }
981
982 89
        if (! method_exists($class, $methodName)) {
983
            throw MappingException::entityListenerMethodNotFound($class, $methodName, $this->className);
984
        }
985
986
        // Check if entity listener already got registered and ignore it if positive
987
        if (in_array($listener, $this->entityListeners[$eventName] ?? [], true)) {
988
            return;
989
        }
990
991 89
        $this->entityListeners[$eventName][] = $listener;
992
    }
993 89
994
    /**
995 89
     * Sets the discriminator column definition.
996 75
     *
997
     * @see getDiscriminatorColumn()
998 75
     *
999
     * @throws MappingException
1000
     */
1001 88
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1002
    {
1003
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1004
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1005 88
        }
1006 83
1007
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1008 88
1009
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1010 1031
1011
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1012 1031
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1013
        }
1014
1015 367
        $this->discriminatorColumn = $discriminatorColumn;
1016
    }
1017 367
1018 367
    /**
1019
     * Sets the discriminator values used by this class.
1020 398
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1021
     *
1022 398
     * @param string[] $map
1023 398
     *
1024
     * @throws MappingException
1025
     */
1026
    public function setDiscriminatorMap(array $map) : void
1027
    {
1028
        foreach ($map as $value => $className) {
1029 2
            $this->addDiscriminatorMapClass($value, $className);
1030
        }
1031 2
    }
1032 2
1033
    /**
1034
     * Adds one entry of the discriminator map with a new class and corresponding name.
1035
     *
1036
     * @param string|int $name
1037 446
     *
1038
     * @throws MappingException
1039 446
     */
1040
    public function addDiscriminatorMapClass($name, string $className) : void
1041
    {
1042 1091
        $this->discriminatorMap[$name] = $className;
1043
1044 1091
        if ($this->className === $className) {
1045
            $this->discriminatorValue = $name;
1046
1047
            return;
1048
        }
1049
1050
        if (! (class_exists($className) || interface_exists($className))) {
1051
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1052
        }
1053
1054
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1055
            $this->subClasses[] = $className;
1056
        }
1057
    }
1058
1059
    public function getValueGenerationPlan() : ValueGenerationPlan
1060
    {
1061
        return $this->valueGenerationPlan;
1062
    }
1063
1064
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1065
    {
1066
        $this->valueGenerationPlan = $valueGenerationPlan;
1067
    }
1068
1069
    public function checkPropertyDuplication(string $columnName) : bool
1070
    {
1071
        return isset($this->fieldNames[$columnName])
1072
            || ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName);
1073
    }
1074
1075
    /**
1076
     * Marks this class as read only, no change tracking is applied to it.
1077
     */
1078
    public function asReadOnly() : void
1079
    {
1080
        $this->readOnly = true;
1081
    }
1082
1083
    /**
1084
     * Whether this class is read only or not.
1085
     */
1086
    public function isReadOnly() : bool
1087
    {
1088
        return $this->readOnly;
1089
    }
1090
1091
    public function isVersioned() : bool
1092
    {
1093
        return $this->versionProperty !== null;
1094
    }
1095
1096
    /**
1097
     * Map Embedded Class
1098
     *
1099
     * @param mixed[] $mapping
1100
     *
1101
     * @throws MappingException
1102
     */
1103
    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

1103
    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...
1104
    {
1105
        /*if (isset($this->properties[$mapping['fieldName']])) {
1106
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1107
        }
1108
1109
        $this->embeddedClasses[$mapping['fieldName']] = [
1110
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1111
            'columnPrefix'   => $mapping['columnPrefix'],
1112
            'declaredField'  => $mapping['declaredField'] ?? null,
1113
            'originalField'  => $mapping['originalField'] ?? null,
1114
            'declaringClass' => $this,
1115
        ];*/
1116
    }
1117
1118
    /**
1119
     * Inline the embeddable class
1120
     *
1121
     * @param string $property
1122
     */
1123
    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

1123
    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

1123
    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...
1124
    {
1125
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1126
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1127
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1128
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1129
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1130
                ? $property . '.' . $fieldMapping['declaredField']
1131
                : $property;
1132
1133
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1134
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1135
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1136
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1137
                    $property,
1138
                    $fieldMapping['columnName'],
1139
                    $this->reflectionClass->getName(),
1140
                    $embeddable->reflectionClass->getName()
1141
                );
1142
            }
1143
1144
            $this->mapField($fieldMapping);
1145
        }*/
1146
    }
1147
}
1148