Failed Conditions
Push — master ( 901929...f6cc12 )
by Jonathan
11:18
created

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

212
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), /** @scrutinizer ignore-type */ $this->discriminatorColumn);
Loading history...
213
        }
214
215
        return $iterator;
216
    }
217
218 11
    public function getAncestorsIterator() : \ArrayIterator
219
    {
220 11
        $ancestors = new \ArrayIterator();
221 11
        $parent    = $this;
222
223 11
        while (($parent = $parent->parent) !== null) {
224 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
225 1
                continue;
226
            }
227
228 7
            $ancestors->append($parent);
229
        }
230
231 11
        return $ancestors;
232
    }
233
234 1257
    public function getRootClassName() : string
235
    {
236 1257
        return ($this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass)
237 403
            ? $this->parent->getRootClassName()
238 1257
            : $this->className
239
        ;
240
    }
241
242
    /**
243
     * Handles metadata cloning nicely.
244
     */
245 13
    public function __clone()
246
    {
247 13
        if ($this->cache) {
248 12
            $this->cache = clone $this->cache;
249
        }
250
251 13
        foreach ($this->declaredProperties as $name => $property) {
252 13
            $this->declaredProperties[$name] = clone $property;
253
        }
254 13
    }
255
256
    /**
257
     * Creates a string representation of this instance.
258
     *
259
     * @return string The string representation of this instance.
260
     *
261
     * @todo Construct meaningful string representation.
262
     */
263
    public function __toString()
264
    {
265
        return __CLASS__ . '@' . spl_object_id($this);
266
    }
267
268
    /**
269
     * Determines which fields get serialized.
270
     *
271
     * It is only serialized what is necessary for best unserialization performance.
272
     * That means any metadata properties that are not set or empty or simply have
273
     * their default value are NOT serialized.
274
     *
275
     * Parts that are also NOT serialized because they can not be properly unserialized:
276
     * - reflectionClass
277
     *
278
     * @return string[] The names of all the fields that should be serialized.
279
     */
280 5
    public function __sleep()
281
    {
282 5
        $serialized = [];
283
284
        // This metadata is always serialized/cached.
285 5
        $serialized = array_merge($serialized, [
286 5
            'declaredProperties',
287
            'fieldNames',
288
            //'embeddedClasses',
289
            'identifier',
290
            'className',
291
            'parent',
292
            'table',
293
            'valueGenerationPlan',
294
        ]);
295
296
        // The rest of the metadata is only serialized if necessary.
297 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
298
            $serialized[] = 'changeTrackingPolicy';
299
        }
300
301 5
        if ($this->customRepositoryClassName) {
302 1
            $serialized[] = 'customRepositoryClassName';
303
        }
304
305 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
306 1
            $serialized[] = 'inheritanceType';
307 1
            $serialized[] = 'discriminatorColumn';
308 1
            $serialized[] = 'discriminatorValue';
309 1
            $serialized[] = 'discriminatorMap';
310 1
            $serialized[] = 'subClasses';
311
        }
312
313 5
        if ($this->isMappedSuperclass) {
314
            $serialized[] = 'isMappedSuperclass';
315
        }
316
317 5
        if ($this->isEmbeddedClass) {
318
            $serialized[] = 'isEmbeddedClass';
319
        }
320
321 5
        if ($this->isVersioned()) {
322
            $serialized[] = 'versionProperty';
323
        }
324
325 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...
326
            $serialized[] = 'lifecycleCallbacks';
327
        }
328
329 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...
330 1
            $serialized[] = 'entityListeners';
331
        }
332
333 5
        if ($this->cache) {
334
            $serialized[] = 'cache';
335
        }
336
337 5
        if ($this->readOnly) {
338 1
            $serialized[] = 'readOnly';
339
        }
340
341 5
        return $serialized;
342
    }
343
344
    /**
345
     * Restores some state that can not be serialized/unserialized.
346
     */
347 1625
    public function wakeupReflection(ReflectionService $reflectionService) : void
348
    {
349
        // Restore ReflectionClass and properties
350 1625
        $this->reflectionClass = $reflectionService->getClass($this->className);
351
352 1625
        if (! $this->reflectionClass) {
353
            return;
354
        }
355
356 1625
        $this->className = $this->reflectionClass->getName();
357
358 1625
        foreach ($this->declaredProperties as $property) {
359
            /** @var Property $property */
360 1624
            $property->wakeupReflection($reflectionService);
361
        }
362 1625
    }
363
364
    /**
365
     * Validates Identifier.
366
     *
367
     * @throws MappingException
368
     */
369 363
    public function validateIdentifier() : void
370
    {
371 363
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
372 28
            return;
373
        }
374
375
        // Verify & complete identifier mapping
376 363
        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...
377 4
            throw MappingException::identifierRequired($this->className);
378
        }
379
380
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, function (Property $property) : bool {
381 359
            return $property instanceof FieldMetadata
382 359
                && $property->isPrimaryKey()
383 359
                && $property->hasValueGenerator();
384 359
        });
385
386 359
        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...
387
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
388
        }
389 359
    }
390
391
    /**
392
     * Validates association targets actually exist.
393
     *
394
     * @throws MappingException
395
     */
396 362
    public function validateAssociations() : void
397
    {
398 362
        array_map(
399
            function (Property $property) {
400 362
                if (! ($property instanceof AssociationMetadata)) {
401 359
                    return;
402
                }
403
404 249
                $targetEntity = $property->getTargetEntity();
405
406 249
                if (! class_exists($targetEntity)) {
407 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
408
                }
409 362
            },
410 362
            $this->declaredProperties
411
        );
412 361
    }
413
414
    /**
415
     * Validates lifecycle callbacks.
416
     *
417
     * @throws MappingException
418
     */
419 362
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
420
    {
421 362
        foreach ($this->lifecycleCallbacks as $callbacks) {
422
            /** @var array $callbacks */
423 11
            foreach ($callbacks as $callbackFuncName) {
424 11
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
425 11
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
426
                }
427
            }
428
        }
429 361
    }
430
431
    /**
432
     * Sets the change tracking policy used by this class.
433
     */
434 103
    public function setChangeTrackingPolicy(string $policy) : void
435
    {
436 103
        $this->changeTrackingPolicy = $policy;
437 103
    }
438
439
    /**
440
     * Checks whether a field is part of the identifier/primary key field(s).
441
     *
442
     * @param string $fieldName The field name.
443
     *
444
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
445
     */
446 1020
    public function isIdentifier(string $fieldName) : bool
447
    {
448 1020
        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...
449 1
            return false;
450
        }
451
452 1019
        if (! $this->isIdentifierComposite()) {
453 1015
            return $fieldName === $this->identifier[0];
454
        }
455
456 93
        return in_array($fieldName, $this->identifier, true);
457
    }
458
459 1204
    public function isIdentifierComposite() : bool
460
    {
461 1204
        return isset($this->identifier[1]);
462
    }
463
464
    /**
465
     * Validates & completes the basic mapping information for field mapping.
466
     *
467
     * @throws MappingException If something is wrong with the mapping.
468
     */
469 407
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
470
    {
471 407
        $fieldName  = $property->getName();
472 407
        $columnName = $property->getColumnName();
473
474 407
        if (empty($columnName)) {
475 343
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
476
477 343
            $property->setColumnName($columnName);
478
        }
479
480 407
        if (! $this->isMappedSuperclass) {
481 400
            $property->setTableName($this->getTableName());
482
        }
483
484
        // Check for already declared column
485 407
        if (isset($this->fieldNames[$columnName]) ||
486 407
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
487 2
            throw MappingException::duplicateColumnName($this->className, $columnName);
488
        }
489
490
        // Complete id mapping
491 406
        if ($property->isPrimaryKey()) {
492 391
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
493
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
494
            }
495
496 391
            if ($property->getType()->canRequireSQLConversion()) {
497
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($this->className, $property);
498
            }
499
500 391
            if (! in_array($fieldName, $this->identifier, true)) {
501 391
                $this->identifier[] = $fieldName;
502
            }
503
        }
504
505 406
        $this->fieldNames[$columnName] = $fieldName;
506 406
    }
507
508
    /**
509
     * Validates & completes the basic mapping information for field mapping.
510
     *
511
     * @throws MappingException If something is wrong with the mapping.
512
     */
513 20
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
514
    {
515 20
        $this->versionProperty = $property;
516
517 20
        $options = $property->getOptions();
518
519 20
        if (isset($options['default'])) {
520
            return;
521
        }
522
523 20
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'], true)) {
524 18
            $property->setOptions(array_merge($options, ['default' => 1]));
525
526 18
            return;
527
        }
528
529 3
        if ($property->getTypeName() === 'datetime') {
530 2
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
531
532 2
            return;
533
        }
534
535 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
536
    }
537
538
    /**
539
     * Validates & completes the basic mapping information that is common to all
540
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
541
     *
542
     * @throws MappingException If something is wrong with the mapping.
543
     * @throws CacheException   If entity is not cacheable.
544
     */
545 287
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
546
    {
547 287
        $fieldName    = $property->getName();
548 287
        $targetEntity = $property->getTargetEntity();
549
550 287
        if (! $targetEntity) {
551
            throw MappingException::missingTargetEntity($fieldName);
552
        }
553
554 287
        $property->setSourceEntity($this->className);
555 287
        $property->setTargetEntity($targetEntity);
556
557
        // Complete id mapping
558 287
        if ($property->isPrimaryKey()) {
559 47
            if ($property->isOrphanRemoval()) {
560 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
561
            }
562
563 46
            if (! in_array($property->getName(), $this->identifier, true)) {
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
                $this->identifier[] = $property->getName();
573
            }
574
575 46
            if ($this->cache && ! $property->getCache()) {
576 2
                throw NonCacheableEntityAssociation::fromEntityAndField($this->className, $fieldName);
577
            }
578
579 44
            if ($property instanceof ToManyAssociationMetadata) {
580 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
581
            }
582
        }
583
584
        // Cascades
585 283
        $cascadeTypes = ['remove', 'persist', 'refresh'];
586 283
        $cascades     = array_map('strtolower', $property->getCascade());
587
588 283
        if (in_array('all', $cascades, true)) {
589 6
            $cascades = $cascadeTypes;
590
        }
591
592 283
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
593 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
594
595 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
596
        }
597
598 282
        $property->setCascade($cascades);
599 282
    }
600
601
    /**
602
     * Validates & completes a to-one association mapping.
603
     *
604
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
605
     *
606
     * @throws \RuntimeException
607
     * @throws MappingException
608
     */
609 247
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
610
    {
611 247
        $fieldName = $property->getName();
612
613 247
        if ($property->isOwningSide()) {
614 243
            if (empty($property->getJoinColumns())) {
615
                // Apply default join column
616 86
                $property->addJoinColumn(new JoinColumnMetadata());
617
            }
618
619 243
            $uniqueConstraintColumns = [];
620
621 243
            foreach ($property->getJoinColumns() as $joinColumn) {
622
                /** @var JoinColumnMetadata $joinColumn */
623 243
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
624 112
                    if (count($property->getJoinColumns()) === 1) {
625 110
                        if (! $property->isPrimaryKey()) {
626 110
                            $joinColumn->setUnique(true);
627
                        }
628
                    } else {
629 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
630
                    }
631
                }
632
633 243
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
634
635 243
                if (! $joinColumn->getColumnName()) {
636 99
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
637
                }
638
639 243
                if (! $joinColumn->getReferencedColumnName()) {
640 86
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
641
                }
642
643 243
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
644
            }
645
646 243
            if ($uniqueConstraintColumns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniqueConstraintColumns 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...
647 2
                if (! $this->table) {
648
                    throw new \RuntimeException(
649
                        'ClassMetadata::setTable() has to be called before defining a one to one relationship.'
650
                    );
651
                }
652
653 2
                $this->table->addUniqueConstraint(
654
                    [
655 2
                        'name'    => sprintf('%s_uniq', $fieldName),
656 2
                        'columns' => $uniqueConstraintColumns,
657
                        'options' => [],
658
                        'flags'   => [],
659
                    ]
660
                );
661
            }
662
        }
663
664 247
        if ($property->isOrphanRemoval()) {
665 7
            $cascades = $property->getCascade();
666
667 7
            if (! in_array('remove', $cascades, true)) {
668 6
                $cascades[] = 'remove';
669
670 6
                $property->setCascade($cascades);
671
            }
672
673
            // @todo guilhermeblanco where is this used?
674
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
675
            //$property->setUnique(false);
676
        }
677
678 247
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
679 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
680
        }
681 246
    }
682
683
    /**
684
     * Validates & completes a to-many association mapping.
685
     *
686
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
687
     *
688
     * @throws MappingException
689
     */
690 172
    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

690
    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...
691
    {
692
        // Do nothing
693 172
    }
694
695
    /**
696
     * Validates & completes a one-to-one association mapping.
697
     *
698
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
699
     */
700 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

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

918
                /** @scrutinizer ignore-call */ 
919
                $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...
919
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
920
            }
921
922 25
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
923
                ? $property->getJoinTable()->getInverseJoinColumns()
924 25
                : $property->getJoinColumns()
925
            ;
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 440
        return $columns;
941
    }
942
943
    /**
944
     * Gets the name of the primary table.
945
     */
946 1580
    public function getTableName() : ?string
947
    {
948 1580
        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
969
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
970 7
        return $schema . $this->getTableName() . '_id_tmp';
971
    }
972
973
    /**
974
     * Sets the mapped subclasses of this class.
975
     *
976
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
977
     *
978
     * @param string[] $subclasses The names of all mapped subclasses.
979
     */
980 4
    public function setSubclasses(array $subclasses) : void
981
    {
982 4
        foreach ($subclasses as $subclass) {
983 3
            $this->subClasses[] = $subclass;
984
        }
985 4
    }
986
987
    /**
988
     * @return string[]
989
     */
990 1073
    public function getSubClasses() : array
991
    {
992 1073
        return $this->subClasses;
993
    }
994
995
    /**
996
     * Sets the inheritance type used by the class and its subclasses.
997
     *
998
     * @param int $type
999
     *
1000
     * @throws MappingException
1001
     */
1002 118
    public function setInheritanceType($type) : void
1003
    {
1004 118
        if (! $this->isInheritanceType($type)) {
1005
            throw MappingException::invalidInheritanceType($this->className, $type);
1006
        }
1007
1008 118
        $this->inheritanceType = $type;
1009 118
    }
1010
1011
    /**
1012
     * Sets the override property mapping for an entity relationship.
1013
     *
1014
     * @throws \RuntimeException
1015
     * @throws MappingException
1016
     * @throws CacheException
1017
     */
1018 12
    public function setPropertyOverride(Property $property) : void
1019
    {
1020 12
        $fieldName = $property->getName();
1021
1022 12
        if (! isset($this->declaredProperties[$fieldName])) {
1023 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1024
        }
1025
1026 10
        $originalProperty          = $this->getProperty($fieldName);
1027 10
        $originalPropertyClassName = get_class($originalProperty);
1028
1029
        // If moving from transient to persistent, assume it's a new property
1030 10
        if ($originalPropertyClassName === TransientMetadata::class) {
1031 1
            unset($this->declaredProperties[$fieldName]);
1032
1033 1
            $this->addProperty($property);
1034
1035 1
            return;
1036
        }
1037
1038
        // Do not allow to change property type
1039 9
        if ($originalPropertyClassName !== get_class($property)) {
1040
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1041
        }
1042
1043
        // Do not allow to change version property
1044 9
        if ($originalProperty instanceof VersionFieldMetadata) {
1045
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1046
        }
1047
1048 9
        unset($this->declaredProperties[$fieldName]);
1049
1050 9
        if ($property instanceof FieldMetadata) {
1051
            // Unset defined fieldName prior to override
1052 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

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

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

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

1071
                $originalProperty->/** @scrutinizer ignore-call */ 
1072
                                   setFetchMode($property->getFetchMode());
Loading history...
1072
            }
1073
1074 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

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

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

1083
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
1084 9
    }
1085
1086
    /**
1087
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1088
     *
1089
     * @return bool
1090
     */
1091 337
    public function isRootEntity()
1092
    {
1093 337
        return $this->className === $this->getRootClassName();
1094
    }
1095
1096
    /**
1097
     * Checks whether a mapped field is inherited from a superclass.
1098
     *
1099
     * @param string $fieldName
1100
     *
1101
     * @return bool TRUE if the field is inherited, FALSE otherwise.
1102
     */
1103 621
    public function isInheritedProperty($fieldName)
1104
    {
1105 621
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1106
1107 621
        return ! ($declaringClass->className === $this->className);
1108
    }
1109
1110
    /**
1111
     * {@inheritdoc}
1112
     */
1113 441
    public function setTable(TableMetadata $table) : void
1114
    {
1115 441
        $this->table = $table;
1116
1117 441
        if (empty($table->getName())) {
1118
            $table->setName($this->namingStrategy->classToTableName($this->className));
1119
        }
1120 441
    }
1121
1122
    /**
1123
     * Checks whether the given type identifies an inheritance type.
1124
     *
1125
     * @param int $type
1126
     *
1127
     * @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
1128
     */
1129 118
    private function isInheritanceType($type)
1130
    {
1131 118
        return $type === InheritanceType::NONE
1132 92
            || $type === InheritanceType::SINGLE_TABLE
1133 50
            || $type === InheritanceType::JOINED
1134 118
            || $type === InheritanceType::TABLE_PER_CLASS;
1135
    }
1136
1137 909
    public function getColumn(string $columnName) : ?LocalColumnMetadata
1138
    {
1139 909
        foreach ($this->declaredProperties as $property) {
1140 909
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1141 909
                return $property;
1142
            }
1143
        }
1144
1145
        return null;
1146
    }
1147
1148
    /**
1149
     * Add a property mapping.
1150
     *
1151
     * @throws \RuntimeException
1152
     * @throws MappingException
1153
     * @throws CacheException
1154
     */
1155 431
    public function addProperty(Property $property)
1156
    {
1157 431
        $fieldName = $property->getName();
1158
1159
        // Check for empty field name
1160 431
        if (empty($fieldName)) {
1161 1
            throw MappingException::missingFieldName($this->className);
1162
        }
1163
1164 430
        $property->setDeclaringClass($this);
1165
1166
        switch (true) {
1167 430
            case ($property instanceof VersionFieldMetadata):
1168 20
                $this->validateAndCompleteFieldMapping($property);
1169 20
                $this->validateAndCompleteVersionFieldMapping($property);
1170 19
                break;
1171
1172 429
            case ($property instanceof FieldMetadata):
1173 406
                $this->validateAndCompleteFieldMapping($property);
1174 405
                break;
1175
1176 290
            case ($property instanceof OneToOneAssociationMetadata):
1177 131
                $this->validateAndCompleteAssociationMapping($property);
1178 130
                $this->validateAndCompleteToOneAssociationMetadata($property);
1179 129
                $this->validateAndCompleteOneToOneMapping($property);
1180 129
                break;
1181
1182 226
            case ($property instanceof OneToManyAssociationMetadata):
1183 116
                $this->validateAndCompleteAssociationMapping($property);
1184 116
                $this->validateAndCompleteToManyAssociationMetadata($property);
1185 116
                $this->validateAndCompleteOneToManyMapping($property);
1186 116
                break;
1187
1188 222
            case ($property instanceof ManyToOneAssociationMetadata):
1189 150
                $this->validateAndCompleteAssociationMapping($property);
1190 147
                $this->validateAndCompleteToOneAssociationMetadata($property);
1191 147
                $this->validateAndCompleteManyToOneMapping($property);
1192 147
                break;
1193
1194 126
            case ($property instanceof ManyToManyAssociationMetadata):
1195 110
                $this->validateAndCompleteAssociationMapping($property);
1196 109
                $this->validateAndCompleteToManyAssociationMetadata($property);
1197 109
                $this->validateAndCompleteManyToManyMapping($property);
1198 109
                break;
1199
1200
            default:
1201
                // Transient properties are ignored on purpose here! =)
1202 30
                break;
1203
        }
1204
1205 422
        $this->addDeclaredProperty($property);
1206 422
    }
1207
1208
    /**
1209
     * INTERNAL:
1210
     * Adds a property mapping without completing/validating it.
1211
     * This is mainly used to add inherited property mappings to derived classes.
1212
     */
1213 98
    public function addInheritedProperty(Property $property)
1214
    {
1215 98
        $inheritedProperty = clone $property;
1216 98
        $declaringClass    = $property->getDeclaringClass();
1217
1218 98
        if ($inheritedProperty instanceof FieldMetadata) {
1219 97
            if (! $declaringClass->isMappedSuperclass) {
1220 75
                $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

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

1448
    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...
1449
    {
1450
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
1451
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1452
        }
1453
1454
        $this->embeddedClasses[$mapping['fieldName']] = [
1455
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1456
            'columnPrefix'   => $mapping['columnPrefix'],
1457
            'declaredField'  => $mapping['declaredField'] ?? null,
1458
            'originalField'  => $mapping['originalField'] ?? null,
1459
            'declaringClass' => $this,
1460
        ];*/
1461
    }
1462
1463
    /**
1464
     * Inline the embeddable class
1465
     *
1466
     * @param string $property
1467
     */
1468
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
0 ignored issues
show
Unused Code introduced by
The parameter $embeddable is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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

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

1468
    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...
1469
    {
1470
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
1471
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1472
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1473
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1474
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1475
                ? $property . '.' . $fieldMapping['declaredField']
1476
                : $property;
1477
1478
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1479
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1480
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1481
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1482
                    $property,
1483
                    $fieldMapping['columnName'],
1484
                    $this->reflectionClass->getName(),
1485
                    $embeddable->reflectionClass->getName()
1486
                );
1487
            }
1488
1489
            $this->mapField($fieldMapping);
1490
        }*/
1491
    }
1492
}
1493