Failed Conditions
Pull Request — master (#7898)
by Guilherme
63:09
created

ClassMetadata::isIdentifier()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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

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

205
        $this->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
206 98
        $this->setIdentifier($parent->identifier);
207 98
        $this->setChangeTrackingPolicy($parent->changeTrackingPolicy);
208 98
209
        if ($parent->discriminatorColumn) {
210 98
            $this->setDiscriminatorColumn($parent->discriminatorColumn);
211 70
            $this->setDiscriminatorMap($parent->discriminatorMap);
212 70
        }
213
214
        if ($parent->isMappedSuperclass) {
215 98
            $this->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
216 27
        }
217
218
        if ($parent->cache) {
219 98
            $this->setCache(clone $parent->cache);
220 3
        }
221
222
        if (! empty($parent->lifecycleCallbacks)) {
223 98
            $this->lifecycleCallbacks = $parent->lifecycleCallbacks;
224 5
        }
225
226
        if (! empty($parent->entityListeners)) {
227 98
            $this->entityListeners = $parent->entityListeners;
228 7
        }
229
    }
230 98
231
    public function setClassName(string $className)
232
    {
233
        $this->className = $className;
234
    }
235
236
    public function getAncestorsIterator() : ArrayIterator
237
    {
238
        $ancestors = new ArrayIterator();
239
        $parent    = $this;
240
241
        while (($parent = $parent->parent) !== null) {
242
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
243
                continue;
244
            }
245
246
            $ancestors->append($parent);
247
        }
248 11
249
        return $ancestors;
250 11
    }
251 11
252
    public function getRootClassName() : string
253 11
    {
254 8
        return $this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass
255 1
            ? $this->parent->getRootClassName()
256
            : $this->className;
257
    }
258 7
259
    /**
260
     * Handles metadata cloning nicely.
261 11
     */
262
    public function __clone()
263
    {
264 1260
        if ($this->cache) {
265
            $this->cache = clone $this->cache;
266 1260
        }
267 402
268 1260
        foreach ($this->properties as $name => $property) {
269
            $this->properties[$name] = clone $property;
270
        }
271
    }
272
273
    /**
274 13
     * Creates a string representation of this instance.
275
     *
276 13
     * @return string The string representation of this instance.
277 12
     *
278
     * @todo Construct meaningful string representation.
279
     */
280 13
    public function __toString()
281 13
    {
282
        return self::class . '@' . spl_object_id($this);
283 13
    }
284
285
    /**
286
     * Determines which fields get serialized.
287
     *
288
     * It is only serialized what is necessary for best unserialization performance.
289
     * That means any metadata properties that are not set or empty or simply have
290
     * their default value are NOT serialized.
291
     *
292
     * Parts that are also NOT serialized because they can not be properly unserialized:
293
     * - reflectionClass
294
     *
295
     * @return string[] The names of all the fields that should be serialized.
296
     */
297
    public function __sleep()
298
    {
299
        $serialized = [];
300
301
        // This metadata is always serialized/cached.
302
        $serialized = array_merge($serialized, [
303
            'properties',
304
            'fieldNames',
305
            //'embeddedClasses',
306
            'identifier',
307
            'className',
308
            'parent',
309 4
            'table',
310
            'valueGenerationPlan',
311 4
        ]);
312
313
        // The rest of the metadata is only serialized if necessary.
314 4
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
315 4
            $serialized[] = 'changeTrackingPolicy';
316
        }
317
318
        if ($this->customRepositoryClassName) {
319
            $serialized[] = 'customRepositoryClassName';
320
        }
321
322
        if ($this->inheritanceType !== InheritanceType::NONE) {
323
            $serialized[] = 'inheritanceType';
324
            $serialized[] = 'discriminatorColumn';
325
            $serialized[] = 'discriminatorValue';
326 4
            $serialized[] = 'discriminatorMap';
327
            $serialized[] = 'subClasses';
328
        }
329
330 4
        if ($this->isMappedSuperclass) {
331 1
            $serialized[] = 'isMappedSuperclass';
332
        }
333
334 4
        if ($this->isEmbeddedClass) {
335 1
            $serialized[] = 'isEmbeddedClass';
336 1
        }
337 1
338 1
        if ($this->isVersioned()) {
339 1
            $serialized[] = 'versionProperty';
340
        }
341
342 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...
343
            $serialized[] = 'lifecycleCallbacks';
344
        }
345
346 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...
347
            $serialized[] = 'entityListeners';
348
        }
349
350 4
        if ($this->cache) {
351
            $serialized[] = 'cache';
352
        }
353
354 4
        if ($this->readOnly) {
355
            $serialized[] = 'readOnly';
356
        }
357
358 4
        return $serialized;
359 1
    }
360
361
    /**
362 4
     * Sets the change tracking policy used by this class.
363
     */
364
    public function setChangeTrackingPolicy(string $policy) : void
365
    {
366 4
        $this->changeTrackingPolicy = $policy;
367 1
    }
368
369
    /**
370 4
     * Checks whether a field is part of the identifier/primary key field(s).
371
     *
372
     * @param string $fieldName The field name.
373
     *
374
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
375
     */
376 104
    public function isIdentifier(string $fieldName) : bool
377
    {
378 104
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
379 104
            return false;
380
        }
381
382
        if (! $this->isIdentifierComposite()) {
383
            return $fieldName === $this->identifier[0];
384
        }
385
386
        return in_array($fieldName, $this->identifier, true);
387
    }
388 1027
389
    public function isIdentifierComposite() : bool
390 1027
    {
391 1
        return isset($this->identifier[1]);
392
    }
393
394 1026
    /**
395 1022
     * Validates Identifier.
396
     *
397
     * @throws MappingException
398 93
     */
399
    public function validateIdentifier() : void
400
    {
401 1213
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
402
            return;
403 1213
        }
404
405
        // Verify & complete identifier mapping
406
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
407
            throw MappingException::identifierRequired($this->className);
408
        }
409
410
        $explicitlyGeneratedProperties = array_filter($this->properties, static function (Property $property) : bool {
411 367
            return $property instanceof FieldMetadata
412
                && $property->isPrimaryKey()
413 367
                && $property->hasValueGenerator();
414 27
        });
415
416
        if ($explicitlyGeneratedProperties && $this->isIdentifierComposite()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $explicitlyGeneratedProperties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
417
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
418 367
        }
419 2
    }
420
421
    /**
422
     * Validates lifecycle callbacks.
423 365
     *
424 365
     * @throws MappingException
425 365
     */
426 365
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
427
    {
428 365
        foreach ($this->lifecycleCallbacks as $callbacks) {
429
            /** @var array $callbacks */
430
            foreach ($callbacks as $callbackFuncName) {
431 365
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
432
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
433
                }
434
            }
435
        }
436
    }
437
438 368
    /**
439
     * {@inheritDoc}
440 368
     */
441
    public function getIdentifierFieldNames()
442 10
    {
443 10
        return $this->identifier;
444 1
    }
445
446
    /**
447
     * Gets the name of the single id field. Note that this only works on
448 367
     * entity classes that have a single-field pk.
449
     *
450
     * @return string
451
     *
452
     * @throws MappingException If the class has a composite primary key.
453 401
     */
454
    public function getSingleIdentifierFieldName()
455 401
    {
456
        if ($this->isIdentifierComposite()) {
457
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
458
        }
459
460
        if (! isset($this->identifier[0])) {
461
            throw MappingException::noIdDefined($this->className);
462
        }
463
464
        return $this->identifier[0];
465
    }
466 151
467
    /**
468 151
     * INTERNAL:
469 1
     * Sets the mapped identifier/primary key fields of this class.
470
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
471
     *
472 150
     * @param mixed[] $identifier
473 1
     */
474
    public function setIdentifier(array $identifier)
475
    {
476 149
        $this->identifier = $identifier;
477
    }
478
479
    /**
480
     * {@inheritDoc}
481
     */
482
    public function getIdentifier()
483
    {
484
        return $this->identifier;
485
    }
486 100
487
    /**
488 100
     * {@inheritDoc}
489 100
     */
490
    public function hasField($fieldName)
491
    {
492
        return isset($this->properties[$fieldName])
493
            && $this->properties[$fieldName] instanceof FieldMetadata;
494 1053
    }
495
496 1053
    /**
497
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
498
     *
499
     * @return ColumnMetadata[]
500
     */
501
    public function getIdentifierColumns(EntityManagerInterface $em) : array
502 189
    {
503
        $columns = [];
504 189
505 189
        foreach ($this->identifier as $idProperty) {
506
            $property = $this->getProperty($idProperty);
507
508
            if ($property instanceof FieldMetadata) {
509
                $columns[$property->getColumnName()] = $property;
510
511
                continue;
512
            }
513 441
514
            /** @var AssociationMetadata $property */
515 441
516
            // Association defined as Id field
517 441
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
518 441
519
            if (! $property->isOwningSide()) {
520 441
                $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

520
                /** @scrutinizer ignore-call */ 
521
                $property = $targetClass->getProperty($property->getMappedBy());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
521 435
            }
522
523 435
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
524
                ? $property->getJoinTable()->getInverseJoinColumns()
525
                : $property->getJoinColumns();
526
527
            foreach ($joinColumns as $joinColumn) {
528
                /** @var JoinColumnMetadata $joinColumn */
529 25
                $columns[$joinColumn->getColumnName()] = $joinColumn;
530
            }
531 25
        }
532
533
        return $columns;
534
    }
535
536 25
    /**
537
     * Gets the name of the primary table.
538 25
     */
539
    public function getTableName() : ?string
540 25
    {
541
        return $this->table->getName();
542 25
    }
543 25
544
    /**
545 25
     * Gets primary table's schema name.
546 13
     */
547
    public function getSchemaName() : ?string
548
    {
549 25
        return $this->table->getSchema();
550
    }
551
552
    /**
553 441
     * Gets the table name to use for temporary identifier tables of this class.
554
     */
555
    public function getTemporaryIdTableName() : string
556
    {
557
        $schema = $this->getSchemaName() === null
558
            ? ''
559 1570
            : $this->getSchemaName() . '_';
560
561 1570
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
562
        return $schema . $this->getTableName() . '_id_tmp';
563
    }
564
565
    /**
566
     * Sets the mapped subclasses of this class.
567 23
     *
568
     * @param string[] $subclasses The names of all mapped subclasses.
569 23
     *
570
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
571
     */
572
    public function setSubclasses(array $subclasses) : void
573
    {
574
        foreach ($subclasses as $subclass) {
575 7
            $this->subClasses[] = $subclass;
576
        }
577 7
    }
578 6
579 7
    /**
580
     * @return string[]
581
     */
582 7
    public function getSubClasses() : array
583
    {
584
        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 4
     * @throws MappingException
593
     */
594 4
    public function setInheritanceType($type) : void
595 3
    {
596
        if (! $this->isInheritanceType($type)) {
597 4
            throw MappingException::invalidInheritanceType($this->className, $type);
598
        }
599
600
        $this->inheritanceType = $type;
601
    }
602 1080
603
    /**
604 1080
     * Sets the override property mapping for an entity relationship.
605
     *
606
     * @throws RuntimeException
607
     * @throws MappingException
608
     * @throws CacheException
609
     */
610
    public function setPropertyOverride(Property $property) : void
611
    {
612
        $fieldName = $property->getName();
613
614 120
        if (! isset($this->properties[$fieldName])) {
615
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
616 120
        }
617
618
        $originalProperty          = $this->getProperty($fieldName);
619
        $originalPropertyClassName = get_class($originalProperty);
620 120
621 120
        // If moving from transient to persistent, assume it's a new property
622
        if ($originalPropertyClassName === TransientMetadata::class) {
623
            unset($this->properties[$fieldName]);
624
625
            $this->addProperty($property);
626
627
            return;
628
        }
629
630 12
        // Do not allow to change property type
631
        if ($originalPropertyClassName !== get_class($property)) {
632 12
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
633
        }
634 12
635 2
        // Do not allow to change version property
636
        if ($originalProperty instanceof FieldMetadata && $originalProperty->isVersioned()) {
637
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
638 10
        }
639 10
640
        unset($this->properties[$fieldName]);
641
642 10
        if ($property instanceof FieldMetadata) {
643 1
            // Unset defined fieldName prior to override
644
            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 1
646
            // Revert what should not be allowed to change
647 1
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
648
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
649
        } elseif ($property instanceof AssociationMetadata) {
650
            // Unset all defined fieldNames prior to override
651 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
652
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
653
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
654
                }
655
            }
656 9
657
            // Override what it should be allowed to change
658
            if ($property->getInversedBy()) {
659
                $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 9
            }
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
                $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 5
            }
665
666
            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 5
            } 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 9
                $originalProperty->setJoinTable($property->getJoinTable());
670
            }
671 9
672 5
            $property = $originalProperty;
673 5
        }
674
675
        $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
    }
677
678 9
    /**
679 8
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
680
     *
681
     * @return bool
682 9
     */
683 2
    public function isRootEntity()
684
    {
685
        return $this->className === $this->getRootClassName();
686 9
    }
687 5
688 8
    /**
689 8
     * Checks whether a mapped field is inherited from a superclass.
690
     *
691
     * @param string $fieldName
692 9
     *
693
     * @return bool TRUE if the field is inherited, FALSE otherwise.
694
     */
695 9
    public function isInheritedProperty($fieldName)
696 9
    {
697
        $declaringClass = $this->properties[$fieldName]->getDeclaringClass();
698
699
        return $declaringClass->className !== $this->className;
700
    }
701
702
    /**
703 336
     * {@inheritdoc}
704
     */
705 336
    public function setTable(TableMetadata $table) : void
706
    {
707
        $this->table = $table;
708
709
        // Make sure inherited and declared properties reflect newly defined table
710
        foreach ($this->properties as $property) {
711
            switch (true) {
712
                case $property instanceof FieldMetadata:
713
                    $property->setTableName($property->getTableName() ?? $table->getName());
714
                    break;
715 622
716
                case $property instanceof ToOneAssociationMetadata:
717 622
                    // Resolve association join column table names
718
                    foreach ($property->getJoinColumns() as $joinColumn) {
719 622
                        /** @var JoinColumnMetadata $joinColumn */
720
                        $joinColumn->setTableName($joinColumn->getTableName() ?? $table->getName());
721
                    }
722
723
                    break;
724
            }
725 430
        }
726
    }
727 430
728
    /**
729
     * Checks whether the given type identifies an inheritance type.
730 430
     *
731
     * @param int $type
732 95
     *
733 95
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
734 95
     */
735
    private function isInheritanceType($type)
736 42
    {
737
        return $type === InheritanceType::NONE
738 34
            || $type === InheritanceType::SINGLE_TABLE
739
            || $type === InheritanceType::JOINED
740 34
            || $type === InheritanceType::TABLE_PER_CLASS;
741
    }
742
743 34
    public function getColumn(string $columnName) : ?ColumnMetadata
744
    {
745
        foreach ($this->properties as $property) {
746 430
            switch (true) {
747
                case $property instanceof FieldMetadata:
748
                    if ($property->getColumnName() === $columnName) {
749
                        return $property;
750
                    }
751
752
                    break;
753
754
                case $property instanceof ToOneAssociationMetadata:
755 120
                    foreach ($property->getJoinColumns() as $joinColumn) {
756
                        if ($joinColumn->getColumnName() === $columnName) {
757 120
                            return $joinColumn;
758 93
                        }
759 51
                    }
760 120
761
                    break;
762
            }
763 916
        }
764
765 916
        return null;
766 916
    }
767 916
768
    /**
769
     * Add a property mapping.
770
     *
771
     * @throws RuntimeException
772
     * @throws MappingException
773
     * @throws CacheException
774
     * @throws ReflectionException
775
     */
776
    public function addProperty(Property $property) : void
777
    {
778
        $fieldName = $property->getName();
779
780
        // Check for empty field name
781
        if (empty($fieldName)) {
782 417
            throw MappingException::missingFieldName($this->className);
783
        }
784 417
785
        $property->setDeclaringClass($this);
786
787 417
        switch (true) {
788 1
            case $property instanceof FieldMetadata:
789
                if ($property->isVersioned()) {
790
                    $this->versionProperty = $property;
791 416
                }
792
793
                $this->fieldNames[$property->getColumnName()] = $property->getName();
794 416
                break;
795 406
796 20
            case $property instanceof ToOneAssociationMetadata:
797
                foreach ($property->getJoinColumns() as $joinColumnMetadata) {
798
                    $this->fieldNames[$joinColumnMetadata->getColumnName()] = $property->getName();
799 406
                }
800 406
801
                break;
802 275
803 241
            default:
804 233
                // Transient properties are ignored on purpose here! =)
805
                break;
806
        }
807 241
808
        if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) {
809
            $this->identifier[] = $fieldName;
810
        }
811 178
812
        parent::addProperty($property);
813
    }
814 416
815 395
    /**
816
     * INTERNAL:
817
     * Adds a property mapping without completing/validating it.
818 416
     * This is mainly used to add inherited property mappings to derived classes.
819 416
     */
820
    public function addInheritedProperty(Property $property)
821
    {
822
        if (isset($this->properties[$property->getName()])) {
823
            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 96
        $declaringClass    = $property->getDeclaringClass();
827
        $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property;
828 96
829 1
        if ($inheritedProperty instanceof FieldMetadata) {
830
            if (! $declaringClass->isMappedSuperclass) {
831
                $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 96
            }
833 96
834
            if ($inheritedProperty->isVersioned()) {
835 96
                $this->versionProperty = $inheritedProperty;
836 95
            }
837 73
838
            $this->fieldNames[$property->getColumnName()] = $property->getName();
839
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
840 95
            if ($declaringClass->isMappedSuperclass) {
841 4
                $inheritedProperty->setSourceEntity($this->className);
842
            }
843
844 95
            // Need to add inherited fieldNames
845 43
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
846 42
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
847 10
                    /** @var JoinColumnMetadata $joinColumn */
848
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
849
                }
850
            }
851 42
        }
852 35
853
        $this->properties[$property->getName()] = $inheritedProperty;
854 34
    }
855
856
    /**
857
     * Registers a custom repository class for the entity class.
858
     *
859 96
     * @param string|null $repositoryClassName The class name of the custom mapper.
860 96
     */
861
    public function setCustomRepositoryClassName(?string $repositoryClassName)
862
    {
863
        $this->customRepositoryClassName = $repositoryClassName;
864
    }
865
866
    public function getCustomRepositoryClassName() : ?string
867 30
    {
868
        return $this->customRepositoryClassName;
869 30
    }
870 30
871
    /**
872 163
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
873
     *
874 163
     * @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
    public function addLifecycleCallback(string $eventName, string $methodName)
899
    {
900
        if (in_array($methodName, $this->lifecycleCallbacks[$eventName] ?? [], true)) {
901
            return;
902
        }
903
904 16
        $this->lifecycleCallbacks[$eventName][] = $methodName;
905
    }
906 16
907 3
    /**
908
     * Adds a entity listener for entities of this class.
909
     *
910 16
     * @param string $eventName The entity lifecycle event.
911 16
     * @param string $class     The listener class.
912
     * @param string $method    The listener callback method.
913
     *
914
     * @throws MappingException
915
     */
916
    public function addEntityListener(string $eventName, string $class, string $methodName) : void
917
    {
918
        $listener = [
919
            'class'  => $class,
920
            'method' => $methodName,
921
        ];
922 13
923
        if (! class_exists($class)) {
924
            throw MappingException::entityListenerClassNotFound($class, $this->className);
925 13
        }
926 13
927
        if (! method_exists($class, $methodName)) {
928
            throw MappingException::entityListenerMethodNotFound($class, $methodName, $this->className);
929 13
        }
930 1
931
        // Check if entity listener already got registered and ignore it if positive
932
        if (in_array($listener, $this->entityListeners[$eventName] ?? [], true)) {
933 12
            return;
934 1
        }
935
936
        $this->entityListeners[$eventName][] = $listener;
937
    }
938 11
939 5
    /**
940
     * Sets the discriminator column definition.
941
     *
942 11
     * @see getDiscriminatorColumn()
943 11
     *
944
     * @throws MappingException
945
     */
946
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
947
    {
948
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
949
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
950
        }
951
952 94
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
953
954 94
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
955
956
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
957
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
958 94
        }
959
960 94
        $this->discriminatorColumn = $discriminatorColumn;
961
    }
962 94
963
    /**
964
     * Sets the discriminator values used by this class.
965
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
966 94
     *
967 94
     * @param string[] $map
968
     *
969
     * @throws MappingException
970
     */
971
    public function setDiscriminatorMap(array $map) : void
972
    {
973
        foreach ($map as $value => $className) {
974
            $this->addDiscriminatorMapClass($value, $className);
975
        }
976
    }
977 89
978
    /**
979 89
     * Adds one entry of the discriminator map with a new class and corresponding name.
980 89
     *
981
     * @param string|int $name
982 89
     *
983
     * @throws MappingException
984
     */
985
    public function addDiscriminatorMapClass($name, string $className) : void
986
    {
987
        $this->discriminatorMap[$name] = $className;
988
989
        if ($this->className === $className) {
990
            $this->discriminatorValue = $name;
991 89
992
            return;
993 89
        }
994
995 89
        if (! (class_exists($className) || interface_exists($className))) {
996 75
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
997
        }
998 75
999
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1000
            $this->subClasses[] = $className;
1001 88
        }
1002
    }
1003
1004
    public function getValueGenerationPlan() : ValueGenerationPlan
1005 88
    {
1006 83
        return $this->valueGenerationPlan;
1007
    }
1008 88
1009
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1010 1031
    {
1011
        $this->valueGenerationPlan = $valueGenerationPlan;
1012 1031
    }
1013
1014
    public function checkPropertyDuplication(string $columnName) : bool
1015 367
    {
1016
        return isset($this->fieldNames[$columnName])
1017 367
            || ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName);
1018 367
    }
1019
1020 398
    /**
1021
     * Marks this class as read only, no change tracking is applied to it.
1022 398
     */
1023 398
    public function asReadOnly() : void
1024
    {
1025
        $this->readOnly = true;
1026
    }
1027
1028
    /**
1029 2
     * Whether this class is read only or not.
1030
     */
1031 2
    public function isReadOnly() : bool
1032 2
    {
1033
        return $this->readOnly;
1034
    }
1035
1036
    public function isVersioned() : bool
1037 446
    {
1038
        return $this->versionProperty !== null;
1039 446
    }
1040
1041
    /**
1042 1091
     * Map Embedded Class
1043
     *
1044 1091
     * @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