Failed Conditions
Push — master ( becf73...b9880b )
by Guilherme
09:53
created

ClassMetadata::validateIdentifier()   B

Complexity

Conditions 8
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 8.048

Importance

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

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

225
        $this->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
226 98
        $this->setIdentifier($parent->identifier);
227 98
        $this->setLifecycleCallbacks($parent->lifecycleCallbacks);
228 98
        $this->setChangeTrackingPolicy($parent->changeTrackingPolicy);
229
230 98
        if ($parent->discriminatorColumn) {
231 70
            $this->setDiscriminatorColumn($parent->discriminatorColumn);
232 70
            $this->setDiscriminatorMap($parent->discriminatorMap);
233
        }
234
235 98
        if ($parent->isMappedSuperclass) {
236 27
            $this->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
237
        }
238
239 98
        if ($parent->cache) {
240 3
            $this->setCache(clone $parent->cache);
241
        }
242
243 98
        if (! empty($parent->entityListeners)) {
244 7
            $this->entityListeners = $parent->entityListeners;
245
        }
246 98
    }
247
248
    public function setClassName(string $className)
249
    {
250
        $this->className = $className;
251
    }
252
253
    public function getColumnsIterator() : ArrayIterator
254
    {
255
        $iterator = parent::getColumnsIterator();
256
257
        if ($this->discriminatorColumn) {
258
            $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

258
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), /** @scrutinizer ignore-type */ $this->discriminatorColumn);
Loading history...
259
        }
260
261
        return $iterator;
262
    }
263
264 11
    public function getAncestorsIterator() : ArrayIterator
265
    {
266 11
        $ancestors = new ArrayIterator();
267 11
        $parent    = $this;
268
269 11
        while (($parent = $parent->parent) !== null) {
270 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
271 1
                continue;
272
            }
273
274 7
            $ancestors->append($parent);
275
        }
276
277 11
        return $ancestors;
278
    }
279
280 1260
    public function getRootClassName() : string
281
    {
282 1260
        return $this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass
283 402
            ? $this->parent->getRootClassName()
284 1260
            : $this->className;
285
    }
286
287
    /**
288
     * Handles metadata cloning nicely.
289
     */
290 13
    public function __clone()
291
    {
292 13
        if ($this->cache) {
293 12
            $this->cache = clone $this->cache;
294
        }
295
296 13
        foreach ($this->declaredProperties as $name => $property) {
297 13
            $this->declaredProperties[$name] = clone $property;
298
        }
299 13
    }
300
301
    /**
302
     * Creates a string representation of this instance.
303
     *
304
     * @return string The string representation of this instance.
305
     *
306
     * @todo Construct meaningful string representation.
307
     */
308
    public function __toString()
309
    {
310
        return self::class . '@' . spl_object_id($this);
311
    }
312
313
    /**
314
     * Determines which fields get serialized.
315
     *
316
     * It is only serialized what is necessary for best unserialization performance.
317
     * That means any metadata properties that are not set or empty or simply have
318
     * their default value are NOT serialized.
319
     *
320
     * Parts that are also NOT serialized because they can not be properly unserialized:
321
     * - reflectionClass
322
     *
323
     * @return string[] The names of all the fields that should be serialized.
324
     */
325 5
    public function __sleep()
326
    {
327 5
        $serialized = [];
328
329
        // This metadata is always serialized/cached.
330 5
        $serialized = array_merge($serialized, [
331 5
            'declaredProperties',
332
            'fieldNames',
333
            //'embeddedClasses',
334
            'identifier',
335
            'className',
336
            'parent',
337
            'table',
338
            'valueGenerationPlan',
339
        ]);
340
341
        // The rest of the metadata is only serialized if necessary.
342 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
343
            $serialized[] = 'changeTrackingPolicy';
344
        }
345
346 5
        if ($this->customRepositoryClassName) {
347 1
            $serialized[] = 'customRepositoryClassName';
348
        }
349
350 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
351 1
            $serialized[] = 'inheritanceType';
352 1
            $serialized[] = 'discriminatorColumn';
353 1
            $serialized[] = 'discriminatorValue';
354 1
            $serialized[] = 'discriminatorMap';
355 1
            $serialized[] = 'subClasses';
356
        }
357
358 5
        if ($this->isMappedSuperclass) {
359
            $serialized[] = 'isMappedSuperclass';
360
        }
361
362 5
        if ($this->isEmbeddedClass) {
363
            $serialized[] = 'isEmbeddedClass';
364
        }
365
366 5
        if ($this->isVersioned()) {
367
            $serialized[] = 'versionProperty';
368
        }
369
370 5
        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...
371
            $serialized[] = 'lifecycleCallbacks';
372
        }
373
374 5
        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...
375 1
            $serialized[] = 'entityListeners';
376
        }
377
378 5
        if ($this->cache) {
379
            $serialized[] = 'cache';
380
        }
381
382 5
        if ($this->readOnly) {
383 1
            $serialized[] = 'readOnly';
384
        }
385
386 5
        return $serialized;
387
    }
388
389
    /**
390
     * Restores some state that can not be serialized/unserialized.
391
     */
392 1634
    public function wakeupReflection(ReflectionService $reflectionService) : void
393
    {
394
        // Restore ReflectionClass and properties
395 1634
        $this->reflectionClass = $reflectionService->getClass($this->className);
396
397 1634
        if (! $this->reflectionClass) {
398
            return;
399
        }
400
401 1634
        $this->className = $this->reflectionClass->getName();
402
403 1634
        foreach ($this->declaredProperties as $property) {
404
            /** @var Property $property */
405 1633
            $property->wakeupReflection($reflectionService);
406
        }
407 1634
    }
408
409
    /**
410
     * Sets the change tracking policy used by this class.
411
     */
412 104
    public function setChangeTrackingPolicy(string $policy) : void
413
    {
414 104
        $this->changeTrackingPolicy = $policy;
415 104
    }
416
417
    /**
418
     * Checks whether a field is part of the identifier/primary key field(s).
419
     *
420
     * @param string $fieldName The field name.
421
     *
422
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
423
     */
424 1027
    public function isIdentifier(string $fieldName) : bool
425
    {
426 1027
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
427 1
            return false;
428
        }
429
430 1026
        if (! $this->isIdentifierComposite()) {
431 1022
            return $fieldName === $this->identifier[0];
432
        }
433
434 93
        return in_array($fieldName, $this->identifier, true);
435
    }
436
437 1215
    public function isIdentifierComposite() : bool
438
    {
439 1215
        return isset($this->identifier[1]);
440
    }
441
442
    /**
443
     * Validates Identifier.
444
     *
445
     * @throws MappingException
446
     */
447 371
    public function validateIdentifier() : void
448
    {
449 371
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
450 27
            return;
451
        }
452
453
        // Verify & complete identifier mapping
454 371
        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...
455 4
            throw MappingException::identifierRequired($this->className);
456
        }
457
458
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, static function (Property $property) : bool {
459 367
            return $property instanceof FieldMetadata
460 367
                && $property->isPrimaryKey()
461 367
                && $property->hasValueGenerator();
462 367
        });
463
464 367
        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...
465
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
466
        }
467 367
    }
468
469
    /**
470
     * Validates association targets actually exist.
471
     *
472
     * @throws MappingException
473
     */
474 370
    public function validateAssociations() : void
475
    {
476 370
        array_map(
477
            function (Property $property) {
478 370
                if (! ($property instanceof AssociationMetadata)) {
479 367
                    return;
480
                }
481
482 251
                $targetEntity = $property->getTargetEntity();
483
484 251
                if (! class_exists($targetEntity)) {
485 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
486
                }
487 370
            },
488 370
            $this->declaredProperties
489
        );
490 369
    }
491
492
    /**
493
     * Validates lifecycle callbacks.
494
     *
495
     * @throws MappingException
496
     */
497 370
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
498
    {
499 370
        foreach ($this->lifecycleCallbacks as $callbacks) {
500
            /** @var array $callbacks */
501 10
            foreach ($callbacks as $callbackFuncName) {
502 10
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
503 1
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
504
                }
505
            }
506
        }
507 369
    }
508
509
    /**
510
     * Validates & completes the basic mapping information for field mapping.
511
     *
512
     * @throws MappingException If something is wrong with the mapping.
513
     */
514 21
    protected function validateAndCompleteVersionFieldMapping(FieldMetadata $property)
515
    {
516 21
        $this->versionProperty = $property;
517
518 21
        $options = $property->getOptions();
519
520 21
        if (isset($options['default'])) {
521
            return;
522
        }
523
524 21
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'], true)) {
525 19
            $property->setOptions(array_merge($options, ['default' => 1]));
526
527 19
            return;
528
        }
529
530 3
        if (in_array($property->getTypeName(), ['datetime', 'datetime_immutable', 'datetimetz', 'datetimetz_immutable'], true)) {
531 2
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
532
533 2
            return;
534
        }
535
536 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
537
    }
538
539
    /**
540
     * Validates & completes the basic mapping information that is common to all
541
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
542
     *
543
     * @throws MappingException If something is wrong with the mapping.
544
     * @throws CacheException   If entity is not cacheable.
545
     */
546 291
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
547
    {
548 291
        $fieldName    = $property->getName();
549 291
        $targetEntity = $property->getTargetEntity();
550
551 291
        if (! $targetEntity) {
552
            throw MappingException::missingTargetEntity($fieldName);
553
        }
554
555 291
        $property->setSourceEntity($this->className);
556 291
        $property->setTargetEntity($targetEntity);
557
558
        // Complete id mapping
559 291
        if ($property->isPrimaryKey()) {
560 47
            if ($property->isOrphanRemoval()) {
561 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
562
            }
563
564 46
            if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
565
                throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
566
                    $property->getTargetEntity(),
567
                    $this->className,
568
                    $fieldName
569
                );
570
            }
571
572 46
            if ($this->cache && ! $property->getCache()) {
573 2
                throw NonCacheableEntityAssociation::fromEntityAndField($this->className, $fieldName);
574
            }
575
576 44
            if ($property instanceof ToManyAssociationMetadata) {
577 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
578
            }
579
580 43
            if (! in_array($property->getName(), $this->identifier, true)) {
581 43
                $this->identifier[] = $property->getName();
582
            }
583
        }
584
585
        // Cascades
586 287
        $cascadeTypes = ['remove', 'persist', 'refresh'];
587 287
        $cascades     = array_map('strtolower', $property->getCascade());
588
589 287
        if (in_array('all', $cascades, true)) {
590 6
            $cascades = $cascadeTypes;
591
        }
592
593 287
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
594 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
595
596 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
597
        }
598
599 286
        $property->setCascade($cascades);
600 286
    }
601
602
    /**
603
     * Validates & completes a to-one association mapping.
604
     *
605
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
606
     *
607
     * @throws RuntimeException
608
     * @throws MappingException
609
     */
610 249
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
611
    {
612 249
        $fieldName = $property->getName();
613
614 249
        if ($property->isOwningSide()) {
615 245
            if (empty($property->getJoinColumns())) {
616
                // Apply default join column
617 86
                $property->addJoinColumn(new JoinColumnMetadata());
618
            }
619
620 245
            $uniqueConstraintColumns = [];
621
622 245
            foreach ($property->getJoinColumns() as $joinColumn) {
623
                /** @var JoinColumnMetadata $joinColumn */
624 245
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
625 112
                    if (count($property->getJoinColumns()) === 1) {
626 110
                        if (! $property->isPrimaryKey()) {
627 110
                            $joinColumn->setUnique(true);
628
                        }
629
                    } else {
630 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
631
                    }
632
                }
633
634 245
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
635
636 245
                if (! $joinColumn->getColumnName()) {
637 100
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
638
                }
639
640 245
                if (! $joinColumn->getReferencedColumnName()) {
641 86
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
642
                }
643
644 245
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
645
            }
646
647 245
            if ($uniqueConstraintColumns) {
648 2
                if (! $this->table) {
649
                    throw new RuntimeException(
650
                        'ClassMetadata::setTable() has to be called before defining a one to one relationship.'
651
                    );
652
                }
653
654 2
                $this->table->addUniqueConstraint(
655
                    [
656 2
                        'name'    => sprintf('%s_uniq', $fieldName),
657 2
                        'columns' => $uniqueConstraintColumns,
658
                        'options' => [],
659
                        'flags'   => [],
660
                    ]
661
                );
662
            }
663
        }
664
665 249
        if ($property->isOrphanRemoval()) {
666 7
            $cascades = $property->getCascade();
667
668 7
            if (! in_array('remove', $cascades, true)) {
669 6
                $cascades[] = 'remove';
670
671 6
                $property->setCascade($cascades);
672
            }
673
674
            // @todo guilhermeblanco where is this used?
675
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
676
            //$property->setUnique(false);
677
        }
678
679 249
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
680 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
681
        }
682 248
    }
683
684
    /**
685
     * Validates & completes a to-many association mapping.
686
     *
687
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
688
     *
689
     * @throws MappingException
690
     */
691 175
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
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

691
    protected function validateAndCompleteToManyAssociationMetadata(/** @scrutinizer ignore-unused */ ToManyAssociationMetadata $property)

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...
692
    {
693
        // Do nothing
694 175
    }
695
696
    /**
697
     * Validates & completes a one-to-one association mapping.
698
     *
699
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
700
     */
701 129
    protected function validateAndCompleteOneToOneMapping(OneToOneAssociationMetadata $property)
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

701
    protected function validateAndCompleteOneToOneMapping(/** @scrutinizer ignore-unused */ OneToOneAssociationMetadata $property)

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...
702
    {
703
        // Do nothing
704 129
    }
705
706
    /**
707
     * Validates & completes a many-to-one association mapping.
708
     *
709
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
710
     *
711
     * @throws MappingException
712
     */
713 149
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
714
    {
715
        // A many-to-one mapping is essentially a one-one backreference
716 149
        if ($property->isOrphanRemoval()) {
717
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
718
        }
719 149
    }
720
721
    /**
722
     * Validates & completes a one-to-many association mapping.
723
     *
724
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
725
     *
726
     * @throws MappingException
727
     */
728 118
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
729
    {
730
        // OneToMany MUST have mappedBy
731 118
        if (! $property->getMappedBy()) {
732
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
733
        }
734
735 118
        if ($property->isOrphanRemoval()) {
736 19
            $cascades = $property->getCascade();
737
738 19
            if (! in_array('remove', $cascades, true)) {
739 16
                $cascades[] = 'remove';
740
741 16
                $property->setCascade($cascades);
742
            }
743
        }
744 118
    }
745
746
    /**
747
     * Validates & completes a many-to-many association mapping.
748
     *
749
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
750
     *
751
     * @throws MappingException
752
     */
753 110
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
754
    {
755 110
        if ($property->isOwningSide()) {
756
            // owning side MUST have a join table
757 97
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
758
759 97
            $property->setJoinTable($joinTable);
760
761 97
            if (! $joinTable->getName()) {
762 18
                $joinTableName = $this->namingStrategy->joinTableName(
763 18
                    $property->getSourceEntity(),
764 18
                    $property->getTargetEntity(),
765 18
                    $property->getName()
766
                );
767
768 18
                $joinTable->setName($joinTableName);
769
            }
770
771 97
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() === $property->getTargetEntity() && ! $joinTable->hasColumns();
772
773 97
            if (! $joinTable->getJoinColumns()) {
774 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
775 16
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
776 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
777 16
                $joinColumn           = new JoinColumnMetadata();
778
779 16
                $joinColumn->setColumnName($columnName);
780 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
781 16
                $joinColumn->setOnDelete('CASCADE');
782
783 16
                $joinTable->addJoinColumn($joinColumn);
784
            }
785
786 97
            if (! $joinTable->getInverseJoinColumns()) {
787 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
788 16
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
789 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
790 16
                $joinColumn           = new JoinColumnMetadata();
791
792 16
                $joinColumn->setColumnName($columnName);
793 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
794 16
                $joinColumn->setOnDelete('CASCADE');
795
796 16
                $joinTable->addInverseJoinColumn($joinColumn);
797
            }
798
799 97
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
800
                /** @var JoinColumnMetadata $joinColumn */
801 97
                if (! $joinColumn->getReferencedColumnName()) {
802 2
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
803
                }
804
805 97
                $referencedColumnName = $joinColumn->getReferencedColumnName();
806
807 97
                if (! $joinColumn->getColumnName()) {
808 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
809 2
                        $property->getSourceEntity(),
810 2
                        $referencedColumnName
811
                    );
812
813 2
                    $joinColumn->setColumnName($columnName);
814
                }
815
            }
816
817 97
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
818
                /** @var JoinColumnMetadata $inverseJoinColumn */
819 97
                if (! $inverseJoinColumn->getReferencedColumnName()) {
820 2
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
821
                }
822
823 97
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
824
825 97
                if (! $inverseJoinColumn->getColumnName()) {
826 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
827 2
                        $property->getTargetEntity(),
828 2
                        $referencedColumnName
829
                    );
830
831 2
                    $inverseJoinColumn->setColumnName($columnName);
832
                }
833
            }
834
        }
835 110
    }
836
837
    /**
838
     * {@inheritDoc}
839
     */
840 401
    public function getIdentifierFieldNames()
841
    {
842 401
        return $this->identifier;
843
    }
844
845
    /**
846
     * Gets the name of the single id field. Note that this only works on
847
     * entity classes that have a single-field pk.
848
     *
849
     * @return string
850
     *
851
     * @throws MappingException If the class has a composite primary key.
852
     */
853 151
    public function getSingleIdentifierFieldName()
854
    {
855 151
        if ($this->isIdentifierComposite()) {
856 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
857
        }
858
859 150
        if (! isset($this->identifier[0])) {
860 1
            throw MappingException::noIdDefined($this->className);
861
        }
862
863 149
        return $this->identifier[0];
864
    }
865
866
    /**
867
     * INTERNAL:
868
     * Sets the mapped identifier/primary key fields of this class.
869
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
870
     *
871
     * @param mixed[] $identifier
872
     */
873 100
    public function setIdentifier(array $identifier)
874
    {
875 100
        $this->identifier = $identifier;
876 100
    }
877
878
    /**
879
     * {@inheritDoc}
880
     */
881 1053
    public function getIdentifier()
882
    {
883 1053
        return $this->identifier;
884
    }
885
886
    /**
887
     * {@inheritDoc}
888
     */
889 189
    public function hasField($fieldName)
890
    {
891 189
        return isset($this->declaredProperties[$fieldName])
892 189
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
893
    }
894
895
    /**
896
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
897
     *
898
     * @return ColumnMetadata[]
899
     */
900 441
    public function getIdentifierColumns(EntityManagerInterface $em) : array
901
    {
902 441
        $columns = [];
903
904 441
        foreach ($this->identifier as $idProperty) {
905 441
            $property = $this->getProperty($idProperty);
906
907 441
            if ($property instanceof FieldMetadata) {
908 435
                $columns[$property->getColumnName()] = $property;
909
910 435
                continue;
911
            }
912
913
            /** @var AssociationMetadata $property */
914
915
            // Association defined as Id field
916 25
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
917
918 25
            if (! $property->isOwningSide()) {
919
                $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

919
                /** @scrutinizer ignore-call */ 
920
                $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...
920
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
921
            }
922
923 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
924
                ? $property->getJoinTable()->getInverseJoinColumns()
925 25
                : $property->getJoinColumns();
926
927 25
            foreach ($joinColumns as $joinColumn) {
928
                /** @var JoinColumnMetadata $joinColumn */
929 25
                $columnName           = $joinColumn->getColumnName();
930 25
                $referencedColumnName = $joinColumn->getReferencedColumnName();
931
932 25
                if (! $joinColumn->getType()) {
933 13
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
934
                }
935
936 25
                $columns[$columnName] = $joinColumn;
937
            }
938
        }
939
940 441
        return $columns;
941
    }
942
943
    /**
944
     * Gets the name of the primary table.
945
     */
946 1585
    public function getTableName() : ?string
947
    {
948 1585
        return $this->table->getName();
949
    }
950
951
    /**
952
     * Gets primary table's schema name.
953
     */
954 14
    public function getSchemaName() : ?string
955
    {
956 14
        return $this->table->getSchema();
957
    }
958
959
    /**
960
     * Gets the table name to use for temporary identifier tables of this class.
961
     */
962 7
    public function getTemporaryIdTableName() : string
963
    {
964 7
        $schema = $this->getSchemaName() === null
965 6
            ? ''
966 7
            : $this->getSchemaName() . '_';
967
968
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
969 7
        return $schema . $this->getTableName() . '_id_tmp';
970
    }
971
972
    /**
973
     * Sets the mapped subclasses of this class.
974
     *
975
     * @param string[] $subclasses The names of all mapped subclasses.
976
     *
977
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
978
     */
979 4
    public function setSubclasses(array $subclasses) : void
980
    {
981 4
        foreach ($subclasses as $subclass) {
982 3
            $this->subClasses[] = $subclass;
983
        }
984 4
    }
985
986
    /**
987
     * @return string[]
988
     */
989 1080
    public function getSubClasses() : array
990
    {
991 1080
        return $this->subClasses;
992
    }
993
994
    /**
995
     * Sets the inheritance type used by the class and its subclasses.
996
     *
997
     * @param int $type
998
     *
999
     * @throws MappingException
1000
     */
1001 120
    public function setInheritanceType($type) : void
1002
    {
1003 120
        if (! $this->isInheritanceType($type)) {
1004
            throw MappingException::invalidInheritanceType($this->className, $type);
1005
        }
1006
1007 120
        $this->inheritanceType = $type;
1008 120
    }
1009
1010
    /**
1011
     * Sets the override property mapping for an entity relationship.
1012
     *
1013
     * @throws RuntimeException
1014
     * @throws MappingException
1015
     * @throws CacheException
1016
     */
1017 12
    public function setPropertyOverride(Property $property) : void
1018
    {
1019 12
        $fieldName = $property->getName();
1020
1021 12
        if (! isset($this->declaredProperties[$fieldName])) {
1022 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1023
        }
1024
1025 10
        $originalProperty          = $this->getProperty($fieldName);
1026 10
        $originalPropertyClassName = get_class($originalProperty);
1027
1028
        // If moving from transient to persistent, assume it's a new property
1029 10
        if ($originalPropertyClassName === TransientMetadata::class) {
1030 1
            unset($this->declaredProperties[$fieldName]);
1031
1032 1
            $this->addProperty($property);
1033
1034 1
            return;
1035
        }
1036
1037
        // Do not allow to change property type
1038 9
        if ($originalPropertyClassName !== get_class($property)) {
1039
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1040
        }
1041
1042
        // Do not allow to change version property
1043 9
        if ($originalProperty instanceof FieldMetadata && $originalProperty->isVersioned()) {
1044
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1045
        }
1046
1047 9
        unset($this->declaredProperties[$fieldName]);
1048
1049 9
        if ($property instanceof FieldMetadata) {
1050
            // Unset defined fieldName prior to override
1051 5
            unset($this->fieldNames[$originalProperty->getColumnName()]);
0 ignored issues
show
Bug introduced by
The method getColumnName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

1051
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
1052
1053
            // Revert what should not be allowed to change
1054 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1055 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1056 9
        } elseif ($property instanceof AssociationMetadata) {
1057
            // Unset all defined fieldNames prior to override
1058 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1059 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1060 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1061
                }
1062
            }
1063
1064
            // Override what it should be allowed to change
1065 9
            if ($property->getInversedBy()) {
1066 2
                $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

1066
                $originalProperty->/** @scrutinizer ignore-call */ 
1067
                                   setInversedBy($property->getInversedBy());
Loading history...
1067
            }
1068
1069 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

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

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

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

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

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

1075
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
1076 4
                $originalProperty->setJoinTable($property->getJoinTable());
1077
            }
1078
1079 9
            $property = $originalProperty;
1080
        }
1081
1082 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

1082
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
1083 9
    }
1084
1085
    /**
1086
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1087
     *
1088
     * @return bool
1089
     */
1090 336
    public function isRootEntity()
1091
    {
1092 336
        return $this->className === $this->getRootClassName();
1093
    }
1094
1095
    /**
1096
     * Checks whether a mapped field is inherited from a superclass.
1097
     *
1098
     * @param string $fieldName
1099
     *
1100
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1101
     */
1102 622
    public function isInheritedProperty($fieldName)
1103
    {
1104 622
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1105
1106 622
        return $declaringClass->className !== $this->className;
1107
    }
1108
1109
    /**
1110
     * {@inheritdoc}
1111
     */
1112 449
    public function setTable(TableMetadata $table) : void
1113
    {
1114 449
        $this->table = $table;
1115
1116 449
        if (empty($table->getName())) {
1117
            $table->setName($this->namingStrategy->classToTableName($this->className));
1118
        }
1119 449
    }
1120
1121
    /**
1122
     * Checks whether the given type identifies an inheritance type.
1123
     *
1124
     * @param int $type
1125
     *
1126
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1127
     */
1128 120
    private function isInheritanceType($type)
1129
    {
1130 120
        return $type === InheritanceType::NONE
1131 93
            || $type === InheritanceType::SINGLE_TABLE
1132 51
            || $type === InheritanceType::JOINED
1133 120
            || $type === InheritanceType::TABLE_PER_CLASS;
1134
    }
1135
1136 916
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1137
    {
1138 916
        foreach ($this->declaredProperties as $property) {
1139 916
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1140 916
                return $property;
1141
            }
1142
        }
1143
1144
        return null;
1145
    }
1146
1147
    /**
1148
     * Add a property mapping.
1149
     *
1150
     * @throws RuntimeException
1151
     * @throws MappingException
1152
     * @throws CacheException
1153
     * @throws ReflectionException
1154
     */
1155 438
    public function addProperty(Property $property)
1156
    {
1157 438
        $fieldName = $property->getName();
1158
1159
        // Check for empty field name
1160 438
        if (empty($fieldName)) {
1161 1
            throw MappingException::missingFieldName($this->className);
1162
        }
1163
1164 437
        $property->setDeclaringClass($this);
1165
1166
        switch (true) {
1167 437
            case $property instanceof FieldMetadata:
1168 412
                $this->fieldNames[$property->getColumnName()] = $property->getName();
1169
1170 412
                if ($property->isVersioned()) {
1171 21
                    $this->validateAndCompleteVersionFieldMapping($property);
1172
                }
1173
1174 411
                break;
1175
1176 294
            case $property instanceof OneToOneAssociationMetadata:
1177 131
                $this->validateAndCompleteAssociationMapping($property);
1178 130
                $this->validateAndCompleteToOneAssociationMetadata($property);
1179 129
                $this->validateAndCompleteOneToOneMapping($property);
1180 129
                break;
1181
1182 230
            case $property instanceof OneToManyAssociationMetadata:
1183 118
                $this->validateAndCompleteAssociationMapping($property);
1184 118
                $this->validateAndCompleteToManyAssociationMetadata($property);
1185 118
                $this->validateAndCompleteOneToManyMapping($property);
1186 118
                break;
1187
1188 225
            case $property instanceof ManyToOneAssociationMetadata:
1189 152
                $this->validateAndCompleteAssociationMapping($property);
1190 149
                $this->validateAndCompleteToOneAssociationMetadata($property);
1191 149
                $this->validateAndCompleteManyToOneMapping($property);
1192 149
                break;
1193
1194 127
            case $property instanceof ManyToManyAssociationMetadata:
1195 111
                $this->validateAndCompleteAssociationMapping($property);
1196 110
                $this->validateAndCompleteToManyAssociationMetadata($property);
1197 110
                $this->validateAndCompleteManyToManyMapping($property);
1198 110
                break;
1199
1200
            default:
1201
                // Transient properties are ignored on purpose here! =)
1202 30
                break;
1203
        }
1204
1205 430
        if ($property->isPrimaryKey() && ! in_array($fieldName, $this->identifier, true)) {
1206 398
            $this->identifier[] = $fieldName;
1207
        }
1208
1209 430
        $this->addDeclaredProperty($property);
1210 430
    }
1211
1212
    /**
1213
     * INTERNAL:
1214
     * Adds a property mapping without completing/validating it.
1215
     * This is mainly used to add inherited property mappings to derived classes.
1216
     */
1217 96
    public function addInheritedProperty(Property $property)
1218
    {
1219 96
        $inheritedProperty = clone $property;
1220 96
        $declaringClass    = $property->getDeclaringClass();
1221
1222 96
        if (isset($this->declaredProperties[$property->getName()])) {
1223 1
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
0 ignored issues
show
Bug introduced by
It seems like $this->getProperty($property->getName()) can also be of type null; however, parameter $property of Doctrine\ORM\Mapping\Map...on::duplicateProperty() does only seem to accept Doctrine\ORM\Mapping\Property, maybe add an additional type check? ( Ignorable by Annotation )

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

1223
            throw MappingException::duplicateProperty($this->className, /** @scrutinizer ignore-type */ $this->getProperty($property->getName()));
Loading history...
1224
        }
1225
1226 96
        if ($inheritedProperty instanceof FieldMetadata) {
1227 95
            if (! $declaringClass->isMappedSuperclass) {
1228 73
                $inheritedProperty->setTableName($property->getTableName());
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

1228
                $inheritedProperty->setTableName($property->/** @scrutinizer ignore-call */ getTableName());
Loading history...
1229
            }
1230
1231 95
            if ($inheritedProperty->isVersioned()) {
1232 4
                $this->versionProperty = $inheritedProperty;
1233
            }
1234
1235 95
            $this->fieldNames[$property->getColumnName()] = $property->getName();
1236 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
1237 42
            if ($declaringClass->isMappedSuperclass) {
1238 10
                $inheritedProperty->setSourceEntity($this->className);
1239
            }
1240
1241
            // Need to add inherited fieldNames
1242 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1243 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1244
                    /** @var JoinColumnMetadata $joinColumn */
1245 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1246
                }
1247
            }
1248
        }
1249
1250 96
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1251 96
    }
1252
1253
    /**
1254
     * Registers a custom repository class for the entity class.
1255
     *
1256
     * @param string|null $repositoryClassName The class name of the custom mapper.
1257
     */
1258 30
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1259
    {
1260 30
        $this->customRepositoryClassName = $repositoryClassName;
1261 30
    }
1262
1263 163
    public function getCustomRepositoryClassName() : ?string
1264
    {
1265 163
        return $this->customRepositoryClassName;
1266
    }
1267
1268
    /**
1269
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1270
     *
1271
     * @param string $lifecycleEvent
1272
     *
1273
     * @return bool
1274
     */
1275
    public function hasLifecycleCallbacks($lifecycleEvent)
1276
    {
1277
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1278
    }
1279
1280
    /**
1281
     * Gets the registered lifecycle callbacks for an event.
1282
     *
1283
     * @param string $event
1284
     *
1285
     * @return string[]
1286
     */
1287
    public function getLifecycleCallbacks($event)
1288
    {
1289
        return $this->lifecycleCallbacks[$event] ?? [];
1290
    }
1291
1292
    /**
1293
     * Adds a lifecycle callback for entities of this class.
1294
     *
1295
     * @param string $callback
1296
     * @param string $event
1297
     */
1298 16
    public function addLifecycleCallback($callback, $event)
1299
    {
1300 16
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) {
1301 3
            return;
1302
        }
1303
1304 16
        $this->lifecycleCallbacks[$event][] = $callback;
1305 16
    }
1306
1307
    /**
1308
     * Sets the lifecycle callbacks for entities of this class.
1309
     * Any previously registered callbacks are overwritten.
1310
     *
1311
     * @param string[][] $callbacks
1312
     */
1313 98
    public function setLifecycleCallbacks(array $callbacks) : void
1314
    {
1315 98
        $this->lifecycleCallbacks = $callbacks;
1316 98
    }
1317
1318
    /**
1319
     * Adds a entity listener for entities of this class.
1320
     *
1321
     * @param string $eventName The entity lifecycle event.
1322
     * @param string $class     The listener class.
1323
     * @param string $method    The listener callback method.
1324
     *
1325
     * @throws MappingException
1326
     */
1327 17
    public function addEntityListener(string $eventName, string $class, string $method) : void
1328
    {
1329
        $listener = [
1330 17
            'class'  => $class,
1331 17
            'method' => $method,
1332
        ];
1333
1334 17
        if (! class_exists($class)) {
1335 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1336
        }
1337
1338 16
        if (! method_exists($class, $method)) {
1339 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1340
        }
1341
1342
        // Check if entity listener already got registered and ignore it if positive
1343 15
        if (in_array($listener, $this->entityListeners[$eventName] ?? [], true)) {
1344 5
            return;
1345
        }
1346
1347 15
        $this->entityListeners[$eventName][] = $listener;
1348 15
    }
1349
1350
    /**
1351
     * Sets the discriminator column definition.
1352
     *
1353
     * @see getDiscriminatorColumn()
1354
     *
1355
     * @throws MappingException
1356
     */
1357 94
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1358
    {
1359 94
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1360
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1361
        }
1362
1363 94
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1364
1365 94
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1366
1367 94
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1368
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1369
        }
1370
1371 94
        $this->discriminatorColumn = $discriminatorColumn;
1372 94
    }
1373
1374
    /**
1375
     * Sets the discriminator values used by this class.
1376
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1377
     *
1378
     * @param string[] $map
1379
     *
1380
     * @throws MappingException
1381
     */
1382 89
    public function setDiscriminatorMap(array $map) : void
1383
    {
1384 89
        foreach ($map as $value => $className) {
1385 89
            $this->addDiscriminatorMapClass($value, $className);
1386
        }
1387 89
    }
1388
1389
    /**
1390
     * Adds one entry of the discriminator map with a new class and corresponding name.
1391
     *
1392
     * @param string|int $name
1393
     *
1394
     * @throws MappingException
1395
     */
1396 89
    public function addDiscriminatorMapClass($name, string $className) : void
1397
    {
1398 89
        $this->discriminatorMap[$name] = $className;
1399
1400 89
        if ($this->className === $className) {
1401 75
            $this->discriminatorValue = $name;
1402
1403 75
            return;
1404
        }
1405
1406 88
        if (! (class_exists($className) || interface_exists($className))) {
1407
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1408
        }
1409
1410 88
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1411 83
            $this->subClasses[] = $className;
1412
        }
1413 88
    }
1414
1415 1031
    public function getValueGenerationPlan() : ValueGenerationPlan
1416
    {
1417 1031
        return $this->valueGenerationPlan;
1418
    }
1419
1420 371
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1421
    {
1422 371
        $this->valueGenerationPlan = $valueGenerationPlan;
1423 371
    }
1424
1425
    /**
1426
     * @param $columnName
0 ignored issues
show
Coding Style introduced by
Missing parameter name
Loading history...
1427
     *
1428
     * @return bool
1429
     */
1430 401
    public function checkPropertyDuplication($columnName) : bool
0 ignored issues
show
introduced by
Method \Doctrine\ORM\Mapping\ClassMetadata::checkPropertyDuplication() does not need documentation comment.
Loading history...
1431
    {
1432 401
        return isset($this->fieldNames[$columnName])
1433 401
            || ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName);
1434
    }
1435
1436
    /**
1437
     * Marks this class as read only, no change tracking is applied to it.
1438
     */
1439 2
    public function asReadOnly() : void
1440
    {
1441 2
        $this->readOnly = true;
1442 2
    }
1443
1444
    /**
1445
     * Whether this class is read only or not.
1446
     */
1447 446
    public function isReadOnly() : bool
1448
    {
1449 446
        return $this->readOnly;
1450
    }
1451
1452 1092
    public function isVersioned() : bool
1453
    {
1454 1092
        return $this->versionProperty !== null;
1455
    }
1456
1457
    /**
1458
     * Map Embedded Class
1459
     *
1460
     * @param mixed[] $mapping
1461
     *
1462
     * @throws MappingException
1463
     */
1464
    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

1464
    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...
1465
    {
1466
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
1467
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1468
        }
1469
1470
        $this->embeddedClasses[$mapping['fieldName']] = [
1471
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1472
            'columnPrefix'   => $mapping['columnPrefix'],
1473
            'declaredField'  => $mapping['declaredField'] ?? null,
1474
            'originalField'  => $mapping['originalField'] ?? null,
1475
            'declaringClass' => $this,
1476
        ];*/
1477
    }
1478
1479
    /**
1480
     * Inline the embeddable class
1481
     *
1482
     * @param string $property
1483
     */
1484
    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

1484
    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

1484
    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...
1485
    {
1486
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1487
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1488
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1489
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1490
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1491
                ? $property . '.' . $fieldMapping['declaredField']
1492
                : $property;
1493
1494
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1495
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1496
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1497
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1498
                    $property,
1499
                    $fieldMapping['columnName'],
1500
                    $this->reflectionClass->getName(),
1501
                    $embeddable->reflectionClass->getName()
1502
                );
1503
            }
1504
1505
            $this->mapField($fieldMapping);
1506
        }*/
1507
    }
1508
}
1509