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

ClassMetadata::__sleep()   F

Complexity

Conditions 11
Paths 1024

Size

Total Lines 62
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12.3274

Importance

Changes 0
Metric Value
cc 11
eloc 34
c 0
b 0
f 0
nc 1024
nop 0
dl 0
loc 62
ccs 21
cts 27
cp 0.7778
crap 12.3274
rs 3.15

How to fix   Long Method    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