ClassMetadata   F
last analyzed

Complexity

Total Complexity 157

Size/Duplication

Total Lines 1041
Duplicated Lines 0 %

Test Coverage

Coverage 91.46%

Importance

Changes 0
Metric Value
eloc 283
dl 0
loc 1041
ccs 300
cts 328
cp 0.9146
rs 2
c 0
b 0
f 0
wmc 157

50 Methods

Rating   Name   Duplication   Size   Complexity  
A getIdentifierFieldNames() 0 3 1
A isIdentifierComposite() 0 3 1
A __toString() 0 3 1
A setChangeTrackingPolicy() 0 3 1
A getLifecycleCallbacks() 0 3 1
A getIdentifierColumns() 0 32 6
A addDiscriminatorMapClass() 0 16 6
A hasLifecycleCallbacks() 0 3 1
A setTable() 0 19 5
B setParent() 0 33 7
A isReadOnly() 0 3 1
A setValueGenerationPlan() 0 3 1
A isVersioned() 0 3 1
A setDiscriminatorColumn() 0 15 3
B validateIdentifier() 0 19 8
B getColumn() 0 23 7
A inlineEmbeddable() 0 2 1
A setInheritanceType() 0 7 2
A isIdentifier() 0 11 3
A isInheritanceType() 0 6 4
A getSchemaName() 0 3 1
A setClassName() 0 3 1
A validateLifecycleCallbacks() 0 7 4
A __clone() 0 8 3
A getTableName() 0 3 1
A addLifecycleCallback() 0 7 2
A setIdentifier() 0 3 1
A getIdentifier() 0 3 1
A getSubClasses() 0 3 1
A setSubclasses() 0 4 2
A addEntityListener() 0 21 4
A getSingleIdentifierFieldName() 0 11 3
A isInheritedProperty() 0 5 1
F __sleep() 0 62 11
A mapEmbedded() 0 2 1
A setCustomRepositoryClassName() 0 3 1
A getAncestorsIterator() 0 14 4
A getTemporaryIdTableName() 0 8 2
A getValueGenerationPlan() 0 3 1
A getRootClassName() 0 5 3
A checkPropertyDuplication() 0 4 3
A isRootEntity() 0 3 1
A setDiscriminatorMap() 0 4 2
B addInheritedProperty() 0 34 11
C setPropertyOverride() 0 66 17
A asReadOnly() 0 3 1
A __construct() 0 6 2
A getCustomRepositoryClassName() 0 3 1
A hasField() 0 4 2
B addProperty() 0 37 8

How to fix   Complexity   

Complex Class

Complex classes like ClassMetadata often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMetadata, and based on these observations, apply Extract Interface, too.

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

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

520
                /** @scrutinizer ignore-call */ 
521
                $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...
521
            }
522
523 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
524
                ? $property->getJoinTable()->getInverseJoinColumns()
525 25
                : $property->getJoinColumns();
526
527 25
            foreach ($joinColumns as $joinColumn) {
528 25
                $columns[$joinColumn->getColumnName()] = $joinColumn;
529
            }
530
        }
531
532 442
        return $columns;
533
    }
534
535
    /**
536
     * Gets the name of the primary table.
537
     */
538 1575
    public function getTableName() : ?string
539
    {
540 1575
        return $this->table->getName();
541
    }
542
543
    /**
544
     * Gets primary table's schema name.
545
     */
546 23
    public function getSchemaName() : ?string
547
    {
548 23
        return $this->table->getSchema();
549
    }
550
551
    /**
552
     * Gets the table name to use for temporary identifier tables of this class.
553
     */
554 7
    public function getTemporaryIdTableName() : string
555
    {
556 7
        $schema = $this->getSchemaName() === null
557 6
            ? ''
558 7
            : $this->getSchemaName() . '_';
559
560
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
561 7
        return $schema . $this->getTableName() . '_id_tmp';
562
    }
563
564
    /**
565
     * Sets the mapped subclasses of this class.
566
     *
567
     * @param string[] $subclasses The names of all mapped subclasses.
568
     *
569
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
570
     */
571 4
    public function setSubclasses(array $subclasses) : void
572
    {
573 4
        foreach ($subclasses as $subclass) {
574 3
            $this->subClasses[] = $subclass;
575
        }
576 4
    }
577
578
    /**
579
     * @return string[]
580
     */
581 1081
    public function getSubClasses() : array
582
    {
583 1081
        return $this->subClasses;
584
    }
585
586
    /**
587
     * Sets the inheritance type used by the class and its subclasses.
588
     *
589
     * @param int $type
590
     *
591
     * @throws MappingException
592
     */
593 121
    public function setInheritanceType($type) : void
594
    {
595 121
        if (! $this->isInheritanceType($type)) {
596
            throw MappingException::invalidInheritanceType($this->className, $type);
597
        }
598
599 121
        $this->inheritanceType = $type;
600 121
    }
601
602
    /**
603
     * Sets the override property mapping for an entity relationship.
604
     *
605
     * @throws RuntimeException
606
     * @throws MappingException
607
     * @throws CacheException
608
     */
609 12
    public function setPropertyOverride(Property $property) : void
610
    {
611 12
        $fieldName = $property->getName();
612
613 12
        if (! isset($this->properties[$fieldName])) {
614 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
615
        }
616
617 10
        $originalProperty          = $this->getProperty($fieldName);
618 10
        $originalPropertyClassName = get_class($originalProperty);
619
620
        // If moving from transient to persistent, assume it's a new property
621 10
        if ($originalPropertyClassName === TransientMetadata::class) {
622 1
            unset($this->properties[$fieldName]);
623
624 1
            $this->addProperty($property);
625
626 1
            return;
627
        }
628
629
        // Do not allow to change property type
630 9
        if ($originalPropertyClassName !== get_class($property)) {
631
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
632
        }
633
634
        // Do not allow to change version property
635 9
        if ($originalProperty instanceof FieldMetadata && $originalProperty->isVersioned()) {
636
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
637
        }
638
639 9
        unset($this->properties[$fieldName]);
640
641 9
        if ($property instanceof FieldMetadata) {
642
            // Unset defined fieldName prior to override
643 5
            unset($this->fieldNames[$originalProperty->getColumnName()]);
0 ignored issues
show
Bug introduced by
The method getColumnName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

643
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
644
645
            // Revert what should not be allowed to change
646 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
647 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
648 9
        } elseif ($property instanceof AssociationMetadata) {
649
            // Unset all defined fieldNames prior to override
650 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
651 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
652 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
653
                }
654
            }
655
656
            // Override what it should be allowed to change
657 9
            if ($property->getInversedBy()) {
658 8
                $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

658
                $originalProperty->/** @scrutinizer ignore-call */ 
659
                                   setInversedBy($property->getInversedBy());
Loading history...
659
            }
660
661 9
            if ($property->getFetchMode() !== $originalProperty->getFetchMode()) {
0 ignored issues
show
Bug introduced by
The method getFetchMode() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

661
            if ($property->getFetchMode() !== $originalProperty->/** @scrutinizer ignore-call */ getFetchMode()) {
Loading history...
662 2
                $originalProperty->setFetchMode($property->getFetchMode());
0 ignored issues
show
Bug introduced by
The method setFetchMode() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

662
                $originalProperty->/** @scrutinizer ignore-call */ 
663
                                   setFetchMode($property->getFetchMode());
Loading history...
663
            }
664
665 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) {
0 ignored issues
show
Bug introduced by
The method getJoinColumns() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

665
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinColumns()) {
Loading history...
666 5
                $originalProperty->setJoinColumns($property->getJoinColumns());
667 8
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) {
0 ignored issues
show
Bug introduced by
The method getJoinTable() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

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

667
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
668 8
                $originalProperty->setJoinTable($property->getJoinTable());
669
            }
670
671 9
            $property = $originalProperty;
672
        }
673
674 9
        $this->addProperty($property);
0 ignored issues
show
Bug introduced by
It seems like $property can also be of type null; however, parameter $property of Doctrine\ORM\Mapping\ClassMetadata::addProperty() does only seem to accept Doctrine\ORM\Mapping\Property, maybe add an additional type check? ( Ignorable by Annotation )

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

674
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
675 9
    }
676
677
    /**
678
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
679
     *
680
     * @return bool
681
     */
682 337
    public function isRootEntity()
683
    {
684 337
        return $this->className === $this->getRootClassName();
685
    }
686
687
    /**
688
     * Checks whether a mapped field is inherited from a superclass.
689
     *
690
     * @param string $fieldName
691
     *
692
     * @return bool TRUE if the field is inherited, FALSE otherwise.
693
     */
694 623
    public function isInheritedProperty($fieldName)
695
    {
696 623
        $declaringClass = $this->properties[$fieldName]->getDeclaringClass();
697
698 623
        return $declaringClass->className !== $this->className;
699
    }
700
701
    /**
702
     * {@inheritdoc}
703
     */
704 431
    public function setTable(TableMetadata $table) : void
705
    {
706 431
        $this->table = $table;
707
708
        // Make sure inherited and declared properties reflect newly defined table
709 431
        foreach ($this->properties as $property) {
710
            switch (true) {
711 96
                case $property instanceof FieldMetadata:
712 96
                    $property->setTableName($property->getTableName() ?? $table->getName());
713 96
                    break;
714
715 42
                case $property instanceof ToOneAssociationMetadata:
716
                    // Resolve association join column table names
717 34
                    foreach ($property->getJoinColumns() as $joinColumn) {
718
                        /** @var JoinColumnMetadata $joinColumn */
719 34
                        $joinColumn->setTableName($joinColumn->getTableName() ?? $table->getName());
720
                    }
721
722 34
                    break;
723
            }
724
        }
725 431
    }
726
727
    /**
728
     * Checks whether the given type identifies an inheritance type.
729
     *
730
     * @param int $type
731
     *
732
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
733
     */
734 121
    private function isInheritanceType($type)
735
    {
736 121
        return $type === InheritanceType::NONE
737 94
            || $type === InheritanceType::SINGLE_TABLE
738 52
            || $type === InheritanceType::JOINED
739 121
            || $type === InheritanceType::TABLE_PER_CLASS;
740
    }
741
742 940
    public function getColumn(string $columnName) : ?ColumnMetadata
743
    {
744 940
        foreach ($this->properties as $property) {
745
            switch (true) {
746 940
                case $property instanceof FieldMetadata:
747 940
                    if ($property->getColumnName() === $columnName) {
748 940
                        return $property;
749
                    }
750
751 15
                    break;
752
753 24
                case $property instanceof ToOneAssociationMetadata:
754 12
                    foreach ($property->getJoinColumns() as $joinColumn) {
755 12
                        if ($joinColumn->getColumnName() === $columnName) {
756 12
                            return $joinColumn;
757
                        }
758
                    }
759
760 4
                    break;
761
            }
762
        }
763
764
        return null;
765
    }
766
767
    /**
768
     * Add a property mapping.
769
     *
770
     * @throws RuntimeException
771
     * @throws MappingException
772
     * @throws CacheException
773
     * @throws ReflectionException
774
     */
775 418
    public function addProperty(Property $property) : void
776
    {
777 418
        $fieldName = $property->getName();
778
779
        // Check for empty field name
780 418
        if (empty($fieldName)) {
781 1
            throw MappingException::missingFieldName($this->className);
782
        }
783
784 417
        $property->setDeclaringClass($this);
785
786
        switch (true) {
787 417
            case $property instanceof FieldMetadata:
788 407
                if ($property->isVersioned()) {
789 20
                    $this->versionProperty = $property;
790
                }
791
792 407
                $this->fieldNames[$property->getColumnName()] = $property->getName();
793 407
                break;
794
795 276
            case $property instanceof ToOneAssociationMetadata:
796 241
                foreach ($property->getJoinColumns() as $joinColumnMetadata) {
797 233
                    $this->fieldNames[$joinColumnMetadata->getColumnName()] = $property->getName();
798
                }
799
800 241
                break;
801
802
            default:
803
                // Transient properties are ignored on purpose here! =)
804 179
                break;
805
        }
806
807 417
        if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) {
808 396
            $this->identifier[] = $fieldName;
809
        }
810
811 417
        parent::addProperty($property);
812 417
    }
813
814
    /**
815
     * INTERNAL:
816
     * Adds a property mapping without completing/validating it.
817
     * This is mainly used to add inherited property mappings to derived classes.
818
     */
819 97
    public function addInheritedProperty(Property $property)
820
    {
821 97
        if (isset($this->properties[$property->getName()])) {
822 1
            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

822
            throw MappingException::duplicateProperty($this->className, /** @scrutinizer ignore-type */ $this->getProperty($property->getName()));
Loading history...
823
        }
824
825 97
        $declaringClass    = $property->getDeclaringClass();
826 97
        $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property;
827
828 97
        if ($inheritedProperty instanceof FieldMetadata) {
829 96
            if (! $declaringClass->isMappedSuperclass) {
830 74
                $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

830
                $inheritedProperty->setTableName($property->/** @scrutinizer ignore-call */ getTableName());
Loading history...
831
            }
832
833 96
            if ($inheritedProperty->isVersioned()) {
834 4
                $this->versionProperty = $inheritedProperty;
835
            }
836
837 96
            $this->fieldNames[$property->getColumnName()] = $property->getName();
838 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
839 42
            if ($declaringClass->isMappedSuperclass) {
840 10
                $inheritedProperty->setSourceEntity($this->className);
841
            }
842
843
            // Need to add inherited fieldNames
844 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
845 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
846
                    /** @var JoinColumnMetadata $joinColumn */
847 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
848
                }
849
            }
850
        }
851
852 97
        $this->properties[$property->getName()] = $inheritedProperty;
853 97
    }
854
855
    /**
856
     * Registers a custom repository class for the entity class.
857
     *
858
     * @param string|null $repositoryClassName The class name of the custom mapper.
859
     */
860 30
    public function setCustomRepositoryClassName(?string $repositoryClassName)
861
    {
862 30
        $this->customRepositoryClassName = $repositoryClassName;
863 30
    }
864
865 164
    public function getCustomRepositoryClassName() : ?string
866
    {
867 164
        return $this->customRepositoryClassName;
868
    }
869
870
    /**
871
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
872
     *
873
     * @param string $lifecycleEvent
874
     *
875
     * @return bool
876
     */
877
    public function hasLifecycleCallbacks($lifecycleEvent)
878
    {
879
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
880
    }
881
882
    /**
883
     * Gets the registered lifecycle callbacks for an event.
884
     *
885
     * @param string $event
886
     *
887
     * @return string[]
888
     */
889
    public function getLifecycleCallbacks($event) : array
890
    {
891
        return $this->lifecycleCallbacks[$event] ?? [];
892
    }
893
894
    /**
895
     * Adds a lifecycle callback for entities of this class.
896
     */
897 16
    public function addLifecycleCallback(string $eventName, string $methodName)
898
    {
899 16
        if (in_array($methodName, $this->lifecycleCallbacks[$eventName] ?? [], true)) {
900 3
            return;
901
        }
902
903 16
        $this->lifecycleCallbacks[$eventName][] = $methodName;
904 16
    }
905
906
    /**
907
     * Adds a entity listener for entities of this class.
908
     *
909
     * @param string $eventName The entity lifecycle event.
910
     * @param string $class     The listener class.
911
     * @param string $method    The listener callback method.
912
     *
913
     * @throws MappingException
914
     */
915 13
    public function addEntityListener(string $eventName, string $class, string $methodName) : void
916
    {
917
        $listener = [
918 13
            'class'  => $class,
919 13
            'method' => $methodName,
920
        ];
921
922 13
        if (! class_exists($class)) {
923 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
924
        }
925
926 12
        if (! method_exists($class, $methodName)) {
927 1
            throw MappingException::entityListenerMethodNotFound($class, $methodName, $this->className);
928
        }
929
930
        // Check if entity listener already got registered and ignore it if positive
931 11
        if (in_array($listener, $this->entityListeners[$eventName] ?? [], true)) {
932 5
            return;
933
        }
934
935 11
        $this->entityListeners[$eventName][] = $listener;
936 11
    }
937
938
    /**
939
     * Sets the discriminator column definition.
940
     *
941
     * @see getDiscriminatorColumn()
942
     *
943
     * @throws MappingException
944
     */
945 95
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
946
    {
947 95
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
948
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
949
        }
950
951 95
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
952
953 95
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
954
955 95
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
956
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
957
        }
958
959 95
        $this->discriminatorColumn = $discriminatorColumn;
960 95
    }
961
962
    /**
963
     * Sets the discriminator values used by this class.
964
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
965
     *
966
     * @param string[] $map
967
     *
968
     * @throws MappingException
969
     */
970 90
    public function setDiscriminatorMap(array $map) : void
971
    {
972 90
        foreach ($map as $value => $className) {
973 90
            $this->addDiscriminatorMapClass($value, $className);
974
        }
975 90
    }
976
977
    /**
978
     * Adds one entry of the discriminator map with a new class and corresponding name.
979
     *
980
     * @param string|int $name
981
     *
982
     * @throws MappingException
983
     */
984 90
    public function addDiscriminatorMapClass($name, string $className) : void
985
    {
986 90
        $this->discriminatorMap[$name] = $className;
987
988 90
        if ($this->className === $className) {
989 76
            $this->discriminatorValue = $name;
990
991 76
            return;
992
        }
993
994 89
        if (! (class_exists($className) || interface_exists($className))) {
995
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
996
        }
997
998 89
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
999 84
            $this->subClasses[] = $className;
1000
        }
1001 89
    }
1002
1003 1032
    public function getValueGenerationPlan() : ValueGenerationPlan
1004
    {
1005 1032
        return $this->valueGenerationPlan;
1006
    }
1007
1008 368
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1009
    {
1010 368
        $this->valueGenerationPlan = $valueGenerationPlan;
1011 368
    }
1012
1013 399
    public function checkPropertyDuplication(string $columnName) : bool
1014
    {
1015 399
        return isset($this->fieldNames[$columnName])
1016 399
            || ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName);
1017
    }
1018
1019
    /**
1020
     * Marks this class as read only, no change tracking is applied to it.
1021
     */
1022 2
    public function asReadOnly() : void
1023
    {
1024 2
        $this->readOnly = true;
1025 2
    }
1026
1027
    /**
1028
     * Whether this class is read only or not.
1029
     */
1030 447
    public function isReadOnly() : bool
1031
    {
1032 447
        return $this->readOnly;
1033
    }
1034
1035 1092
    public function isVersioned() : bool
1036
    {
1037 1092
        return $this->versionProperty !== null;
1038
    }
1039
1040
    /**
1041
     * Map Embedded Class
1042
     *
1043
     * @param mixed[] $mapping
1044
     *
1045
     * @throws MappingException
1046
     */
1047
    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

1047
    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...
1048
    {
1049
        /*if (isset($this->properties[$mapping['fieldName']])) {
1050
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1051
        }
1052
1053
        $this->embeddedClasses[$mapping['fieldName']] = [
1054
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1055
            'columnPrefix'   => $mapping['columnPrefix'],
1056
            'declaredField'  => $mapping['declaredField'] ?? null,
1057
            'originalField'  => $mapping['originalField'] ?? null,
1058
            'declaringClass' => $this,
1059
        ];*/
1060
    }
1061
1062
    /**
1063
     * Inline the embeddable class
1064
     *
1065
     * @param string $property
1066
     */
1067
    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

1067
    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

1067
    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...
1068
    {
1069
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1070
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1071
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1072
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1073
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1074
                ? $property . '.' . $fieldMapping['declaredField']
1075
                : $property;
1076
1077
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1078
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1079
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1080
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1081
                    $property,
1082
                    $fieldMapping['columnName'],
1083
                    $this->reflectionClass->getName(),
1084
                    $embeddable->reflectionClass->getName()
1085
                );
1086
            }
1087
1088
            $this->mapField($fieldMapping);
1089
        }*/
1090
    }
1091
}
1092