Completed
Pull Request — master (#8122)
by Gildas
64:47
created

ClassMetadata::setValueGenerationPlan()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

207
        $this->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
208 99
        $this->setIdentifier($parent->identifier);
209
        $this->setChangeTrackingPolicy($parent->changeTrackingPolicy);
210 99
211 71
        if ($parent->discriminatorColumn) {
212 71
            $this->setDiscriminatorColumn($parent->discriminatorColumn);
213
            $this->setDiscriminatorMap($parent->discriminatorMap);
214
        }
215 99
216 27
        if ($parent->isMappedSuperclass) {
217
            $this->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
218
        }
219 99
220 3
        if ($parent->cache) {
221
            $this->setCache(clone $parent->cache);
222
        }
223 99
224 5
        if (! empty($parent->lifecycleCallbacks)) {
225
            $this->lifecycleCallbacks = $parent->lifecycleCallbacks;
226
        }
227 99
228 7
        if (! empty($parent->entityListeners)) {
229
            $this->entityListeners = $parent->entityListeners;
230 99
        }
231
    }
232
233
    public function setClassName(string $className)
234
    {
235
        $this->className = $className;
236
    }
237
238
    public function getColumnsIterator() : ArrayIterator
239
    {
240
        $iterator = parent::getColumnsIterator();
241
242
        if ($this->discriminatorColumn) {
243
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), $this->discriminatorColumn);
0 ignored issues
show
Bug introduced by
$this->discriminatorColumn of type Doctrine\ORM\Mapping\DiscriminatorColumnMetadata is incompatible with the type string expected by parameter $newval of ArrayIterator::offsetSet(). ( Ignorable by Annotation )

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

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

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

665
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
666
667 5
            // Revert what should not be allowed to change
668 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
669 9
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
670
        } elseif ($property instanceof AssociationMetadata) {
671 9
            // Unset all defined fieldNames prior to override
672 5
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
673 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
674
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
675
                }
676
            }
677
678 9
            // Override what it should be allowed to change
679 8
            if ($property->getInversedBy()) {
680
                $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

680
                $originalProperty->/** @scrutinizer ignore-call */ 
681
                                   setInversedBy($property->getInversedBy());
Loading history...
681
            }
682 9
683 2
            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

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

684
                $originalProperty->/** @scrutinizer ignore-call */ 
685
                                   setFetchMode($property->getFetchMode());
Loading history...
685
            }
686 9
687 5
            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

687
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinColumns()) {
Loading history...
688 8
                $originalProperty->setJoinColumns($property->getJoinColumns());
689 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

689
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
690
                $originalProperty->setJoinTable($property->getJoinTable());
691
            }
692 9
693
            $property = $originalProperty;
694
        }
695 9
696 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

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

830
            throw MappingException::duplicateProperty($this->className, /** @scrutinizer ignore-type */ $this->getProperty($property->getName()));
Loading history...
831
        }
832 97
833 97
        $declaringClass    = $property->getDeclaringClass();
834
        $inheritedProperty = $declaringClass->isMappedSuperclass ? clone $property : $property;
835 97
836 96
        if ($inheritedProperty instanceof FieldMetadata) {
837 74
            if (! $declaringClass->isMappedSuperclass) {
838
                $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

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

1060
    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...
1061
    {
1062
        /*if (isset($this->properties[$mapping['fieldName']])) {
1063
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1064
        }
1065
1066
        $this->embeddedClasses[$mapping['fieldName']] = [
1067
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1068
            'columnPrefix'   => $mapping['columnPrefix'],
1069
            'declaredField'  => $mapping['declaredField'] ?? null,
1070
            'originalField'  => $mapping['originalField'] ?? null,
1071
            'declaringClass' => $this,
1072
        ];*/
1073
    }
1074
1075
    /**
1076
     * Inline the embeddable class
1077
     *
1078
     * @param string $property
1079
     */
1080
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

1080
    public function inlineEmbeddable(/** @scrutinizer ignore-unused */ $property, ClassMetadata $embeddable) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $embeddable is not used and could be removed. ( Ignorable by Annotation )

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

1080
    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...
1081
    {
1082
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1083
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1084
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1085
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1086
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1087
                ? $property . '.' . $fieldMapping['declaredField']
1088
                : $property;
1089
1090
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1091
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1092
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1093
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1094
                    $property,
1095
                    $fieldMapping['columnName'],
1096
                    $this->reflectionClass->getName(),
1097
                    $embeddable->reflectionClass->getName()
1098
                );
1099
            }
1100
1101
            $this->mapField($fieldMapping);
1102
        }*/
1103
    }
1104
}
1105