Failed Conditions
Push — master ( 4bfa22...148895 )
by Guilherme
17:21 queued 09:56
created

ClassMetadata::addProperty()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 7
nop 1
dl 0
loc 29
ccs 13
cts 13
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
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 452
    public function __construct(
183
        string $entityName,
184
        ?ComponentMetadata $parent,
185
        ClassMetadataBuildingContext $metadataBuildingContext
186
    ) {
187 452
        parent::__construct($entityName, $metadataBuildingContext);
188
189 452
        if ($parent) {
190 98
            $this->setParent($parent);
191
        }
192 452
    }
193
194
    /**
195
     * {@inheritdoc}
196
     *
197
     * @throws MappingException
198
     */
199 98
    public function setParent(ComponentMetadata $parent) : void
200
    {
201 98
        parent::setParent($parent);
202
203 98
        foreach ($parent->getPropertiesIterator() as $fieldName => $property) {
204 95
            $this->addInheritedProperty($property);
205
        }
206
207
        // @todo guilhermeblanco Assume to be a ClassMetadata temporarily until ClassMetadata split is complete.
208
        /** @var ClassMetadata $parent */
209 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

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

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

555
                /** @scrutinizer ignore-call */ 
556
                $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...
556
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
557
            }
558
559 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
560
                ? $property->getJoinTable()->getInverseJoinColumns()
561 25
                : $property->getJoinColumns();
562
563 25
            foreach ($joinColumns as $joinColumn) {
564
                /** @var JoinColumnMetadata $joinColumn */
565 25
                $columnName           = $joinColumn->getColumnName();
566 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
567
568 25
                if (! $joinColumn->getType()) {
569 13
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

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

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

687
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
688
689
            // Revert what should not be allowed to change
690 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
691 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
692 9
        } elseif ($property instanceof AssociationMetadata) {
693
            // Unset all defined fieldNames prior to override
694 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
695 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
696 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
697
                }
698
            }
699
700
            // Override what it should be allowed to change
701 9
            if ($property->getInversedBy()) {
702 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

702
                $originalProperty->/** @scrutinizer ignore-call */ 
703
                                   setInversedBy($property->getInversedBy());
Loading history...
703
            }
704
705 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

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

706
                $originalProperty->/** @scrutinizer ignore-call */ 
707
                                   setFetchMode($property->getFetchMode());
Loading history...
707
            }
708
709 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

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

711
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
712 8
                $originalProperty->setJoinTable($property->getJoinTable());
713
            }
714
715 9
            $property = $originalProperty;
716
        }
717
718 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

718
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
719 9
    }
720
721
    /**
722
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
723
     *
724
     * @return bool
725
     */
726 336
    public function isRootEntity()
727
    {
728 336
        return $this->className === $this->getRootClassName();
729
    }
730
731
    /**
732
     * Checks whether a mapped field is inherited from a superclass.
733
     *
734
     * @param string $fieldName
735
     *
736
     * @return bool TRUE if the field is inherited, FALSE otherwise.
737
     */
738 622
    public function isInheritedProperty($fieldName)
739
    {
740 622
        $declaringClass = $this->properties[$fieldName]->getDeclaringClass();
741
742 622
        return $declaringClass->className !== $this->className;
743
    }
744
745
    /**
746
     * {@inheritdoc}
747
     */
748 431
    public function setTable(TableMetadata $table) : void
749
    {
750 431
        $this->table = $table;
751
752
        // Make sure inherited and declared properties reflect newly defined table
753 431
        foreach ($this->properties as $property) {
754
            switch (true) {
755 95
                case $property instanceof FieldMetadata:
756 95
                    $property->setTableName($property->getTableName() ?? $table->getName());
757 95
                    break;
758
759 42
                case $property instanceof ToOneAssociationMetadata:
760
                    // Resolve association join column table names
761 34
                    foreach ($property->getJoinColumns() as $joinColumn) {
762
                        /** @var JoinColumnMetadata $joinColumn */
763 34
                        $joinColumn->setTableName($joinColumn->getTableName() ?? $table->getName());
764
                    }
765
766 34
                    break;
767
            }
768
        }
769 431
    }
770
771
    /**
772
     * Checks whether the given type identifies an inheritance type.
773
     *
774
     * @param int $type
775
     *
776
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
777
     */
778 120
    private function isInheritanceType($type)
779
    {
780 120
        return $type === InheritanceType::NONE
781 93
            || $type === InheritanceType::SINGLE_TABLE
782 51
            || $type === InheritanceType::JOINED
783 120
            || $type === InheritanceType::TABLE_PER_CLASS;
784
    }
785
786 916
    public function getColumn(string $columnName) : ?LocalColumnMetadata
787
    {
788 916
        foreach ($this->properties as $property) {
789 916
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
790 916
                return $property;
791
            }
792
        }
793
794
        return null;
795
    }
796
797
    /**
798
     * Add a property mapping.
799
     *
800
     * @throws RuntimeException
801
     * @throws MappingException
802
     * @throws CacheException
803
     * @throws ReflectionException
804
     */
805 417
    public function addProperty(Property $property) : void
806
    {
807 417
        $fieldName = $property->getName();
808
809
        // Check for empty field name
810 417
        if (empty($fieldName)) {
811 1
            throw MappingException::missingFieldName($this->className);
812
        }
813
814 416
        $property->setDeclaringClass($this);
815
816
        switch (true) {
817 416
            case $property instanceof FieldMetadata:
818 406
                if ($property->isVersioned()) {
819 20
                    $this->versionProperty = $property;
820
                }
821
822 406
                break;
823
824
            default:
825
                // Transient properties are ignored on purpose here! =)
826 275
                break;
827
        }
828
829 416
        if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) {
830 395
            $this->identifier[] = $fieldName;
831
        }
832
833 416
        parent::addProperty($property);
834 416
    }
835
836
    /**
837
     * INTERNAL:
838
     * Adds a property mapping without completing/validating it.
839
     * This is mainly used to add inherited property mappings to derived classes.
840
     */
841 96
    public function addInheritedProperty(Property $property)
842
    {
843 96
        if (isset($this->properties[$property->getName()])) {
844 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

844
            throw MappingException::duplicateProperty($this->className, /** @scrutinizer ignore-type */ $this->getProperty($property->getName()));
Loading history...
845
        }
846
847 96
        $declaringClass    = $property->getDeclaringClass();
848 96
        $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property;
849
850 96
        if ($inheritedProperty instanceof FieldMetadata) {
851 95
            if (! $declaringClass->isMappedSuperclass) {
852 73
                $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

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

1069
    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...
1070
    {
1071
        /*if (isset($this->properties[$mapping['fieldName']])) {
1072
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1073
        }
1074
1075
        $this->embeddedClasses[$mapping['fieldName']] = [
1076
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1077
            'columnPrefix'   => $mapping['columnPrefix'],
1078
            'declaredField'  => $mapping['declaredField'] ?? null,
1079
            'originalField'  => $mapping['originalField'] ?? null,
1080
            'declaringClass' => $this,
1081
        ];*/
1082
    }
1083
1084
    /**
1085
     * Inline the embeddable class
1086
     *
1087
     * @param string $property
1088
     */
1089
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
0 ignored issues
show
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

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

1089
    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...
1090
    {
1091
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1092
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1093
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1094
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1095
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1096
                ? $property . '.' . $fieldMapping['declaredField']
1097
                : $property;
1098
1099
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1100
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1101
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1102
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1103
                    $property,
1104
                    $fieldMapping['columnName'],
1105
                    $this->reflectionClass->getName(),
1106
                    $embeddable->reflectionClass->getName()
1107
                );
1108
            }
1109
1110
            $this->mapField($fieldMapping);
1111
        }*/
1112
    }
1113
}
1114