Failed Conditions
Push — master ( a3e53b...559253 )
by Guilherme
14:58
created

ClassMetadata::getIdentifierColumns()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0702

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 10
nop 1
dl 0
loc 32
ccs 14
cts 16
cp 0.875
crap 6.0702
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;
0 ignored issues
show
Bug introduced by
The type Doctrine\ORM\Utility\PersisterHelper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
introduced by
Type Doctrine\ORM\Utility\PersisterHelper is not used in this file.
Loading history...
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 450
    public function __construct(string $entityName, ?ComponentMetadata $parent)
183
    {
184 450
        parent::__construct($entityName);
185
186 450
        if ($parent) {
187 99
            $this->setParent($parent);
188
        }
189 450
    }
190
191
    /**
192
     * {@inheritdoc}
193
     *
194
     * @throws MappingException
195
     */
196 99
    public function setParent(ComponentMetadata $parent) : void
197
    {
198 99
        parent::setParent($parent);
199
200 99
        foreach ($parent->getPropertiesIterator() as $fieldName => $property) {
201 96
            $this->addInheritedProperty($property);
202
        }
203
204
        // @todo guilhermeblanco Assume to be a ClassMetadata temporarily until ClassMetadata split is complete.
205
        /** @var ClassMetadata $parent */
206 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

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

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

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

659
                $originalProperty->/** @scrutinizer ignore-call */ 
660
                                   setInversedBy($property->getInversedBy());
Loading history...
660
            }
661
662 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

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

663
                $originalProperty->/** @scrutinizer ignore-call */ 
664
                                   setFetchMode($property->getFetchMode());
Loading history...
664
            }
665
666 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

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

668
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
669 8
                $originalProperty->setJoinTable($property->getJoinTable());
670
            }
671
672 9
            $property = $originalProperty;
673
        }
674
675 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

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

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

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

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

1068
    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

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