Test Setup Failed
Push — develop ( 082d66...6f26e1 )
by Guilherme
63:04
created

ClassMetadata::getColumnsIterator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
crap 6
1
<?php
2
3
4
declare(strict_types=1);
5
6
namespace Doctrine\ORM\Mapping;
7
8
use Doctrine\ORM\Cache\CacheException;
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
15
/**
16
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
17
 * of an entity and its associations.
18
 *
19
 * @author Roman Borschel <[email protected]>
20
 * @author Jonathan H. Wage <[email protected]>
21
 * @author Guilherme Blanco <[email protected]>
22
 * @since 2.0
23
 */
24
class ClassMetadata extends ComponentMetadata implements TableOwner
25
{
26
    /**
27
     * The name of the custom repository class used for the entity class.
28
     * (Optional).
29
     *
30
     * @var string
31
     */
32
    protected $customRepositoryClassName;
33
34
    /**
35
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
36
     *
37
     * @var boolean
38
     */
39
    public $isMappedSuperclass = false;
40
41
    /**
42
     * READ-ONLY: Whether this class describes the mapping of an embeddable class.
43
     *
44
     * @var boolean
45
     */
46
    public $isEmbeddedClass = false;
47
48
    /**
49
     * Whether this class describes the mapping of a read-only class.
50
     * That means it is never considered for change-tracking in the UnitOfWork.
51
     * It is a very helpful performance optimization for entities that are immutable,
52
     * either in your domain or through the relation database (coming from a view,
53
     * or a history table for example).
54
     *
55
     * @var boolean
56
     */
57
    private $readOnly = false;
58
59
    /**
60
     * The names of all subclasses (descendants).
61
     *
62
     * @var array
63
     */
64
    protected $subClasses = [];
65
66
    /**
67
     * READ-ONLY: The names of all embedded classes based on properties.
68
     *
69
     * @var array
70
     */
71
    //public $embeddedClasses = [];
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
72
73
    /**
74
     * The named queries allowed to be called directly from Repository.
75
     *
76
     * @var array
77
     */
78
    protected $namedQueries = [];
79
80
    /**
81
     * READ-ONLY: The named native queries allowed to be called directly from Repository.
82
     *
83
     * A native SQL named query definition has the following structure:
84
     * <pre>
85
     * array(
86
     *     'name'               => <query name>,
87
     *     'query'              => <sql query>,
88
     *     'resultClass'        => <class of the result>,
89
     *     'resultSetMapping'   => <name of a SqlResultSetMapping>
90
     * )
91
     * </pre>
92
     *
93
     * @var array
94
     */
95
    public $namedNativeQueries = [];
96
97
    /**
98
     * READ-ONLY: The mappings of the results of native SQL queries.
99
     *
100
     * A native result mapping definition has the following structure:
101
     * <pre>
102
     * array(
103
     *     'name'               => <result name>,
104
     *     'entities'           => array(<entity result mapping>),
105
     *     'columns'            => array(<column result mapping>)
106
     * )
107
     * </pre>
108
     *
109
     * @var array
110
     */
111
    public $sqlResultSetMappings = [];
112
113
    /**
114
     * READ-ONLY: The registered lifecycle callbacks for entities of this class.
115
     *
116
     * @var array<string, array<string>>
117
     */
118
    public $lifecycleCallbacks = [];
119
120
    /**
121
     * READ-ONLY: The registered entity listeners.
122
     *
123
     * @var array
124
     */
125
    public $entityListeners = [];
126
127
    /**
128
     * READ-ONLY: The field names of all fields that are part of the identifier/primary key
129
     * of the mapped entity class.
130
     *
131
     * @var array
132
     */
133
    public $identifier = [];
134
135
    /**
136
     * READ-ONLY: The inheritance mapping type used by the class.
137
     *
138
     * @var string
139
     */
140
    public $inheritanceType = InheritanceType::NONE;
141
142
    /**
143
     * READ-ONLY: The policy used for change-tracking on entities of this class.
144
     *
145
     * @var string
146
     */
147
    public $changeTrackingPolicy = ChangeTrackingPolicy::DEFERRED_IMPLICIT;
148
149
    /**
150
     * READ-ONLY: The discriminator value of this class.
151
     *
152
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
153
     * where a discriminator column is used.</b>
154
     *
155
     * @var mixed
156
     *
157
     * @see discriminatorColumn
158
     */
159
    public $discriminatorValue;
160
161
    /**
162
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
163
     *
164
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
165
     * where a discriminator column is used.</b>
166
     *
167
     * @var mixed
168
     *
169
     * @see discriminatorColumn
170
     */
171
    public $discriminatorMap = [];
172
173
    /**
174
     * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
175
     * inheritance mappings.
176
     *
177
     * @var DiscriminatorColumnMetadata
178
     */
179
    public $discriminatorColumn;
180
181
    /**
182
     * READ-ONLY: The primary table metadata.
183
     *
184
     * @var TableMetadata
185
     */
186
    public $table;
187
188
    /**
189
     * READ-ONLY: An array of field names. Used to look up field names from column names.
190
     * Keys are column names and values are field names.
191
     *
192
     * @var array<string, string>
193
     */
194
    public $fieldNames = [];
195
196
    /**
197
     * READ-ONLY: The field which is used for versioning in optimistic locking (if any).
198
     *
199
     * @var FieldMetadata|null
200
     */
201
    public $versionProperty;
202
203
    /**
204
     * NamingStrategy determining the default column and table names.
205
     *
206
     * @var NamingStrategy
207
     */
208
    protected $namingStrategy;
209
210
    /**
211
     * Value generation plan is responsible for generating values for auto-generated fields.
212
     *
213
     * @var ValueGenerationPlan
214
     */
215
    protected $valueGenerationPlan;
216
217
    /**
218
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
219
     * metadata of the class with the given name.
220
     *
221
     * @param string                       $entityName              The name of the entity class.
222
     * @param ClassMetadataBuildingContext $metadataBuildingContext
223
     */
224
    public function __construct(
225
        string $entityName,
226
        ClassMetadataBuildingContext $metadataBuildingContext
227
    )
228
    {
229
        parent::__construct($entityName);
230
231
        $this->namingStrategy = $metadataBuildingContext->getNamingStrategy();
232
233
        $this->setTable(new TableMetadata());
234
    }
235
236
    /**
237
     * @todo guilhermeblanco Remove once ClassMetadataFactory is finished
238
     *
239
     * @param string $className
240
     */
241
    public function setClassName(string $className)
242
    {
243
        $this->className = $className;
244
    }
245
246
    /**
247
     * @return \ArrayIterator
248
     */
249
    public function getColumnsIterator() : \ArrayIterator
250
    {
251
        $iterator = parent::getColumnsIterator();
252
253
        if ($this->discriminatorColumn) {
254
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), $this->discriminatorColumn);
255
        }
256
257
        return $iterator;
258
    }
259
260
    /**
261
     * @return \ArrayIterator
262
     */
263
    public function getAncestorsIterator() : \ArrayIterator
264
    {
265
        $ancestors = new \ArrayIterator();
266
        $parent    = $this;
267
268
        while (($parent = $parent->parent) !== null) {
269
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
270
                continue;
271
            }
272
273
            $ancestors->append($parent);
274
        }
275
276
        return $ancestors;
277
    }
278
279
    /**
280
     * @return string
281
     */
282
    public function getRootClassName() : string
283
    {
284
        return ($this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass)
285
            ? $this->parent->getRootClassName()
286
            : $this->className
287
        ;
288
    }
289
290
    /**
291
     * Handles metadata cloning nicely.
292
     */
293
    public function __clone()
294
    {
295
        if ($this->cache) {
296
            $this->cache = clone $this->cache;
297
        }
298
299
        foreach ($this->declaredProperties as $name => $property) {
300
            $this->declaredProperties[$name] = clone $property;
301
        }
302
    }
303
304
    /**
305
     * Creates a string representation of this instance.
306
     *
307
     * @return string The string representation of this instance.
308
     *
309
     * @todo Construct meaningful string representation.
310
     */
311
    public function __toString()
312
    {
313
        return __CLASS__ . '@' . spl_object_hash($this);
314
    }
315
316
    /**
317
     * Determines which fields get serialized.
318
     *
319
     * It is only serialized what is necessary for best unserialization performance.
320
     * That means any metadata properties that are not set or empty or simply have
321
     * their default value are NOT serialized.
322
     *
323
     * Parts that are also NOT serialized because they can not be properly unserialized:
324
     * - reflectionClass
325
     *
326
     * @return array The names of all the fields that should be serialized.
327
     */
328
    public function __sleep()
329
    {
330
        $serialized = [];
331
332
        // This metadata is always serialized/cached.
333
        $serialized = array_merge($serialized, [
334
            'declaredProperties',
335
            'fieldNames',
336
            //'embeddedClasses',
337
            'identifier',
338
            'className',
339
            'parent',
340
            'table',
341
            'valueGenerationPlan',
342
        ]);
343
344
        // The rest of the metadata is only serialized if necessary.
345
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
346
            $serialized[] = 'changeTrackingPolicy';
347
        }
348
349
        if ($this->customRepositoryClassName) {
350
            $serialized[] = 'customRepositoryClassName';
351
        }
352
353
        if ($this->inheritanceType !== InheritanceType::NONE) {
354
            $serialized[] = 'inheritanceType';
355
            $serialized[] = 'discriminatorColumn';
356
            $serialized[] = 'discriminatorValue';
357
            $serialized[] = 'discriminatorMap';
358
            $serialized[] = 'subClasses';
359
        }
360
361
        if ($this->isMappedSuperclass) {
362
            $serialized[] = 'isMappedSuperclass';
363
        }
364
365
        if ($this->isEmbeddedClass) {
366
            $serialized[] = 'isEmbeddedClass';
367
        }
368
369
        if ($this->isVersioned()) {
370
            $serialized[] = 'versionProperty';
371
        }
372
373
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks 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...
374
            $serialized[] = 'lifecycleCallbacks';
375
        }
376
377
        if ($this->entityListeners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityListeners 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...
378
            $serialized[] = 'entityListeners';
379
        }
380
381
        if ($this->namedQueries) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namedQueries 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...
382
            $serialized[] = 'namedQueries';
383
        }
384
385
        if ($this->namedNativeQueries) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namedNativeQueries 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...
386
            $serialized[] = 'namedNativeQueries';
387
        }
388
389
        if ($this->sqlResultSetMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sqlResultSetMappings 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...
390
            $serialized[] = 'sqlResultSetMappings';
391
        }
392
393
        if ($this->cache) {
394
            $serialized[] = 'cache';
395
        }
396
397
        if ($this->readOnly) {
398
            $serialized[] = 'readOnly';
399
        }
400
401
        return $serialized;
402
    }
403
404
    /**
405
     * Restores some state that can not be serialized/unserialized.
406
     *
407
     * @param ReflectionService $reflService
408
     *
409
     * @return void
410
     */
411
    public function wakeupReflection(ReflectionService $reflService) : void
412
    {
413
        // Restore ReflectionClass and properties
414
        $this->reflectionClass = $reflService->getClass($this->className);
415
416
        if (! $this->reflectionClass) {
417
            return;
418
        }
419
420
        /*$parentReflFields = [];
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
421
422
        foreach ($this->embeddedClasses as $property => $embeddedClass) {
423
            if (isset($embeddedClass['declaredField'])) {
424
                $parentReflFields[$property] = new ReflectionEmbeddedProperty(
425
                    $parentReflFields[$embeddedClass['declaredField']],
426
                    $reflService->getAccessibleProperty(
427
                        $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
428
                        $embeddedClass['originalField']
429
                    ),
430
                    $this->embeddedClasses[$embeddedClass['declaredField']]['class']
431
                );
432
433
                continue;
434
            }
435
436
            $fieldRefl = $reflService->getAccessibleProperty(
437
                isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->getClassName(),
438
                $property
439
            );
440
441
            // @todo guilhermeblanco Handle reflection initialization once embeddables are back.
442
            $parentReflFields[$property] = $fieldRefl;
443
        }*/
444
445
        foreach ($this->declaredProperties as $field => $property) {
446
            /** @var Property $property */
447
448
            /*if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
449
                // @todo guilhermeblanco Handle reflection initialization once embeddables are back.
450
                $this->reflection[$field] = new ReflectionEmbeddedProperty(
451
                    $parentReflFields[$mapping['declaredField']],
452
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
453
                    $mapping['originalClass']
454
                );
455
                continue;
456
            }*/
457
458
            $property->wakeupReflection($reflService);
459
        }
460
    }
461
462
    /**
463
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
464
     * metadata of the class with the given name.
465
     *
466
     * @param ReflectionService $reflService The reflection service.
467
     *
468
     * @return void
469
     */
470
    public function initializeReflection(ReflectionService $reflService) : void
471
    {
472
        $entityReflClass = $reflService->getClass($this->className);
473
474
        $this->reflectionClass = $entityReflClass;
475
476
        if ($entityReflClass) {
477
            $this->className = $this->reflectionClass->getName();
478
        }
479
    }
480
481
    /**
482
     * Validates Identifier.
483
     *
484
     * @return void
485
     *
486
     * @throws MappingException
487
     */
488
    public function validateIdentifier() : void
489
    {
490
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
491
            return;
492
        }
493
494
        // Verify & complete identifier mapping
495
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier 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...
496
            throw MappingException::identifierRequired($this->className);
497
        }
498
499
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, function (Property $property) : bool {
500
            return $property instanceof FieldMetadata
501
                && $property->isPrimaryKey()
502
                && $property->hasValueGenerator();
503
        });
504
505
        if ($this->isIdentifierComposite() && count($explicitlyGeneratedProperties) !== 0) {
506
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
507
        }
508
    }
509
510
    /**
511
     * Validates association targets actually exist.
512
     *
513
     * @return void
514
     *
515
     * @throws MappingException
516
     */
517
    public function validateAssociations() : void
518
    {
519
        array_map(
520
            function (Property $property) {
521
                if (! ($property instanceof AssociationMetadata)) {
522
                    return;
523
                }
524
525
                $targetEntity = $property->getTargetEntity();
526
527
                if (! class_exists($targetEntity)) {
528
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
529
                }
530
            },
531
            $this->declaredProperties
532
        );
533
    }
534
535
    /**
536
     * Validates lifecycle callbacks.
537
     *
538
     * @param ReflectionService $reflService
539
     *
540
     * @return void
541
     *
542
     * @throws MappingException
543
     */
544
    public function validateLifecycleCallbacks(ReflectionService $reflService) : void
545
    {
546
        foreach ($this->lifecycleCallbacks as $callbacks) {
547
            /** @var array $callbacks */
548
            foreach ($callbacks as $callbackFuncName) {
549
                if (! $reflService->hasPublicMethod($this->className, $callbackFuncName)) {
550
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
551
                }
552
            }
553
        }
554
    }
555
556
    /**
557
     * Sets the change tracking policy used by this class.
558
     *
559
     * @param string $policy
560
     *
561
     * @return void
562
     */
563
    public function setChangeTrackingPolicy(string $policy) : void
564
    {
565
        $this->changeTrackingPolicy = $policy;
566
    }
567
568
    /**
569
     * Checks whether a field is part of the identifier/primary key field(s).
570
     *
571
     * @param string $fieldName The field name.
572
     *
573
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
574
     */
575
    public function isIdentifier(string $fieldName) : bool
576
    {
577
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier 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...
578
            return false;
579
        }
580
581
        if (! $this->isIdentifierComposite()) {
582
            return $fieldName === $this->identifier[0];
583
        }
584
585
        return in_array($fieldName, $this->identifier, true);
586
    }
587 578
588
    /**
589 578
     * @return bool
590 578
     */
591 578
    public function isIdentifierComposite() : bool
592 578
    {
593 578
        return count($this->identifier) > 1;
594
    }
595
596
    /**
597
     * Gets the named query.
598
     *
599
     * @see ClassMetadata::$namedQueries
600 224
     *
601
     * @param string $queryName The query name.
602 224
     *
603
     * @return string
604
     *
605
     * @throws MappingException
606
     */
607
    public function getNamedQuery($queryName) : string
608
    {
609
        if (! isset($this->namedQueries[$queryName])) {
610
            throw MappingException::queryNotFound($this->className, $queryName);
611
        }
612 1
613
        return $this->namedQueries[$queryName];
614 1
    }
615
616
    /**
617
     * Gets all named queries of the class.
618
     *
619
     * @return array
620
     */
621
    public function getNamedQueries() : array
622
    {
623
        return $this->namedQueries;
624
    }
625
626
    /**
627
     * Gets the named native query.
628
     *
629
     * @see ClassMetadata::$namedNativeQueries
630
     *
631
     * @param string $queryName The query name.
632
     *
633
     * @return array
634
     *
635
     * @throws MappingException
636
     */
637
    public function getNamedNativeQuery($queryName) : array
638
    {
639
        if ( ! isset($this->namedNativeQueries[$queryName])) {
640
            throw MappingException::queryNotFound($this->className, $queryName);
641
        }
642
643 466
        return $this->namedNativeQueries[$queryName];
644
    }
645 466
646 91
    /**
647
     * Gets all named native queries of the class.
648 91
     *
649 91
     * @return array
650
     */
651 91
    public function getNamedNativeQueries() : array
652 91
    {
653
        return $this->namedNativeQueries;
654
    }
655
656 91
    /**
657
     * Gets the result set mapping.
658
     *
659 446
     * @see ClassMetadata::$sqlResultSetMappings
660 446
     *
661
     * @param string $name The result set mapping name.
662 446
     *
663 27
     * @return array
664
     *
665
     * @throws MappingException
666 423
     */
667
    public function getSqlResultSetMapping($name)
668
    {
669
        if (! isset($this->sqlResultSetMappings[$name])) {
670
            throw MappingException::resultMappingNotFound($this->className, $name);
671
        }
672
673
        return $this->sqlResultSetMappings[$name];
674
    }
675
676
    /**
677 6
     * Gets all sql result set mappings of the class.
678
     *
679 6
     * @return array
680 6
     */
681
    public function getSqlResultSetMappings()
682 6
    {
683
        return $this->sqlResultSetMappings;
684
    }
685
686
    /**
687
     * Validates & completes the basic mapping information for field mapping.
688
     *
689
     * @param FieldMetadata $property
690
     *
691
     * @throws MappingException If something is wrong with the mapping.
692
     */
693 24
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
694
    {
695 24
        $fieldName  = $property->getName();
696 24
        $columnName = $property->getColumnName();
697
698
        if (empty($columnName)) {
699
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
700
701
            $property->setColumnName($columnName);
702
        }
703
704
        if (! $this->isMappedSuperclass) {
705
            $property->setTableName($this->getTableName());
706 312
        }
707
708 312
        // Check for already declared column
709
        if (isset($this->fieldNames[$columnName]) ||
710
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
711
            throw MappingException::duplicateColumnName($this->className, $columnName);
712
        }
713
714
        // Complete id mapping
715
        if ($property->isPrimaryKey()) {
716
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
717
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
718
            }
719
720
            if ($property->getType()->canRequireSQLConversion()) {
721
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($property);
0 ignored issues
show
Bug introduced by
The call to sqlConversionNotAllowedForPrimaryKeyProperties() misses a required argument $property.

This check looks for function calls that miss required arguments.

Loading history...
Documentation introduced by
$property is of type object<Doctrine\ORM\Mapping\FieldMetadata>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
722
            };
723
724
            if (! in_array($fieldName, $this->identifier)) {
725
                $this->identifier[] = $fieldName;
726
            }
727
        }
728
729
        $this->fieldNames[$columnName] = $fieldName;
730
    }
731
732
    /**
733
     * Validates & completes the basic mapping information for field mapping.
734
     *
735
     * @param VersionFieldMetadata $property
736 7
     *
737
     * @throws MappingException If something is wrong with the mapping.
738
     */
739
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
740 7
    {
741
        $this->versionProperty = $property;
742
743
        $options = $property->getOptions();
744
745
        if (isset($options['default'])) {
746
            return;
747
        }
748
749
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'])) {
750
            $property->setOptions(array_merge($options, ['default' => 1]));
751
752
            return;
753 7
        }
754
755
        if ($property->getTypeName() === 'datetime') {
756
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
757 7
758 1
            return;
759
        }
760
761 7
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
0 ignored issues
show
Documentation introduced by
$property->getType() is of type object<Doctrine\DBAL\Types\Type>|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
762 1
    }
763 1
764 1
    /**
765 1
     * Validates & completes the basic mapping information that is common to all
766 1
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
767 1
     *
768
     * @param AssociationMetadata $property
769
     *
770 7
     * @throws MappingException If something is wrong with the mapping.
771 1
     * @throws CacheException   If entity is not cacheable.
772 1
     */
773
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
774
    {
775
        $fieldName    = $property->getName();
776
        $targetEntity = $property->getTargetEntity();
777 7
778 1
        if (! $targetEntity) {
779
            throw MappingException::missingTargetEntity($fieldName);
780
        }
781 7
782 1
        $property->setSourceEntity($this->className);
783
        $property->setOwningSide($property->getMappedBy() === null);
784
        $property->setTargetEntity($targetEntity);
785 7
786
        // Mandatory and optional attributes for either side
787
        if ($property->getMappedBy()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $property->getMappedBy() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
788
            $property->setOwningSide(false);
789 7
        }
790
791
        // Complete id mapping
792
        if ($property->isPrimaryKey()) {
793 7
            if ($property->isOrphanRemoval()) {
794
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
795
            }
796
797 7
            if ( ! in_array($property->getName(), $this->identifier)) {
798 1
                if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
799
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
800
                        $property->getTargetEntity(),
801 7
                        $this->className,
802 1
                        $fieldName
803
                    );
804
                }
805 7
806
                $this->identifier[] = $property->getName();
807
            }
808
809 7
            if ($this->cache && !$property->getCache()) {
810
                throw CacheException::nonCacheableEntityAssociation($this->className, $fieldName);
811
            }
812
813 7
            if ($property instanceof ToManyAssociationMetadata) {
814 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
815
            }
816
        }
817 7
818
        // Cascades
819
        $cascadeTypes = ['remove', 'persist', 'refresh', 'merge', 'detach'];
820
        $cascades     = array_map('strtolower', $property->getCascade());
821 7
822
        if (in_array('all', $cascades)) {
823
            $cascades = $cascadeTypes;
824
        }
825 7
826
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
827
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
828
829
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
830
        }
831
832
        $property->setCascade($cascades);
833 671
    }
834
835 671
    /**
836
     * Validates & completes a to-one association mapping.
837
     *
838
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
839
     *
840
     * @throws \RuntimeException
841
     * @throws MappingException
842
     */
843
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
844
    {
845 1988
        $fieldName = $property->getName();
846
847
        if ($property->getJoinColumns()) {
848 1988
            $property->setOwningSide(true);
849 1988
        }
850
851 1988
        if ($property->isOwningSide()) {
852
            if (empty($property->getJoinColumns())) {
853
                // Apply default join column
854
                $property->addJoinColumn(new JoinColumnMetadata());
855
            }
856
857
            $uniqueConstraintColumns = [];
858
859
            foreach ($property->getJoinColumns() as $joinColumn) {
860
                /** @var JoinColumnMetadata $joinColumn */
861
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
862
                    if (1 === count($property->getJoinColumns())) {
863
                        if (! $property->isPrimaryKey()) {
864
                            $joinColumn->setUnique(true);
865
                        }
866
                    } else {
867
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
868
                    }
869
                }
870
871 1988
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
872
873
                if (! $joinColumn->getColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getColumnName() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
874
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
875
                }
876
877
                if (! $joinColumn->getReferencedColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getReferencedColumnName() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
878
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
879
                }
880
881 1984
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
882
            }
883 1984
884
            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...
885
                if ( ! $this->table) {
886 1988
                    throw new \RuntimeException(
887 1679
                        "ClassMetadata::setTable() has to be called before defining a one to one relationship."
888
                    );
889 1988
                }
890
891
                $this->table->addUniqueConstraint(
892
                    [
893
                        'name'    => sprintf('%s_uniq', $fieldName),
894
                        'columns' => $uniqueConstraintColumns,
895
                        'options' => [],
896
                        'flags'   => [],
897
                    ]
898
                );
899 543
            }
900
        }
901 543
902
        if ($property->isOrphanRemoval()) {
903 543
            $cascades = $property->getCascade();
904 539
905
            if (! in_array('remove', $cascades)) {
906
                $cascades[] = 'remove';
907 543
908 543
                $property->setCascade($cascades);
909
            }
910 543
911
            // @todo guilhermeblanco where is this used?
912
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
913
            //$property->setUnique(false);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
914
        }
915
916
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
917
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
918
        }
919 385
    }
920
921 385
    /**
922 44
     * Validates & completes a to-many association mapping.
923
     *
924
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
925
     *
926 381
     * @throws MappingException
927 8
     */
928
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
929
    {
930 373
        if ($property->isPrimaryKey()) {
931
            throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
932
        }
933 373
    }
934
935
    /**
936
     * Validates & completes a one-to-one association mapping.
937
     *
938
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
939
     */
940
    protected function validateAndCompleteOneToOneMapping(OneToOneAssociationMetadata $property)
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed.

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

Loading history...
941
    {
942 382
        // Do nothing
943
    }
944 382
945 254
    /**
946 254
     * Validates & completes a many-to-one association mapping.
947
     *
948
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
949 381
     *
950
     * @throws MappingException
951
     */
952
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
953
    {
954
        // A many-to-one mapping is essentially a one-one backreference
955
        if ($property->isOrphanRemoval()) {
956
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
957
        }
958
    }
959
960 382
    /**
961
     * Validates & completes a one-to-many association mapping.
962 382
     *
963 13
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
964 13
     *
965 13
     * @throws MappingException
966
     */
967
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
968
    {
969 381
        // OneToMany MUST be inverse side
970
        $property->setOwningSide(false);
971
972
        // OneToMany MUST have mappedBy
973
        if (! $property->getMappedBy()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $property->getMappedBy() of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
974 538
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
975
        }
976 538
977
        if ($property->isOrphanRemoval()) {
978
            $cascades = $property->getCascade();
979
980
            if (! in_array('remove', $cascades)) {
981
                $cascades[] = 'remove';
982
983
                $property->setCascade($cascades);
984 19
            }
985
        }
986 19
    }
987
988
    /**
989
     * Validates & completes a many-to-many association mapping.
990 19
     *
991 5
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
992
     *
993
     * @throws MappingException
994 19
     */
995 19
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
996
    {
997
        if ($property->isOwningSide()) {
998
            // owning side MUST have a join table
999
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
1000
1001
            $property->setJoinTable($joinTable);
1002
1003 2
            if (! $joinTable->getName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinTable->getName() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1004
                $joinTableName = $this->namingStrategy->joinTableName(
1005 2
                    $property->getSourceEntity(),
1006 2
                    $property->getTargetEntity(),
1007
                    $property->getName()
1008
                );
1009
1010
                $joinTable->setName($joinTableName);
1011
            }
1012
1013
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() == $property->getTargetEntity() && ! $joinTable->hasColumns();
1014 15
1015
            if (! $joinTable->getJoinColumns()) {
1016 15
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
1017 1
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
1018 1
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
1019
                $joinColumn           = new JoinColumnMetadata();
1020
1021
                $joinColumn->setColumnName($columnName);
1022 15
                $joinColumn->setReferencedColumnName($referencedColumnName);
1023 15
                $joinColumn->setOnDelete('CASCADE');
1024
1025
                $joinTable->addJoinColumn($joinColumn);
1026 15
            }
1027
1028
            if (! $joinTable->getInverseJoinColumns()) {
1029
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
1030
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
1031
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
1032
                $joinColumn           = new JoinColumnMetadata();
1033
1034
                $joinColumn->setColumnName($columnName);
1035
                $joinColumn->setReferencedColumnName($referencedColumnName);
1036 123
                $joinColumn->setOnDelete('CASCADE');
1037
1038 123
                $joinTable->addInverseJoinColumn($joinColumn);
1039 123
            }
1040
1041
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
1042
                /** @var JoinColumnMetadata $joinColumn */
1043
                if (! $joinColumn->getReferencedColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getReferencedColumnName() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1044
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
1045
                }
1046 267
1047
                $referencedColumnName = $joinColumn->getReferencedColumnName();
1048 267
1049
                if (! $joinColumn->getColumnName()) {
1050
                    $columnName = $this->namingStrategy->joinKeyColumnName(
1051
                        $property->getSourceEntity(),
1052
                        $referencedColumnName
1053
                    );
1054
1055
                    $joinColumn->setColumnName($columnName);
1056 459
                }
1057
            }
1058 459
1059
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
1060
                /** @var JoinColumnMetadata $inverseJoinColumn */
1061
                if (! $inverseJoinColumn->getReferencedColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inverseJoinColumn->getReferencedColumnName() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1062
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
1063
                }
1064
1065
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
1066 290
1067
                if (! $inverseJoinColumn->getColumnName()) {
1068 290
                    $columnName = $this->namingStrategy->joinKeyColumnName(
1069
                        $property->getTargetEntity(),
1070
                        $referencedColumnName
1071
                    );
1072
1073
                    $inverseJoinColumn->setColumnName($columnName);
1074
                }
1075
            }
1076
        }
1077
    }
1078
1079 1032
    /**
1080
     * {@inheritDoc}
1081 1032
     */
1082 1
    public function getIdentifierFieldNames()
1083
    {
1084
        return $this->identifier;
1085 1031
    }
1086 1026
1087
    /**
1088
     * Gets the name of the single id field. Note that this only works on
1089 91
     * entity classes that have a single-field pk.
1090
     *
1091
     * @return string
1092
     *
1093
     * @throws MappingException If the class has a composite primary key.
1094
     */
1095
    public function getSingleIdentifierFieldName()
1096
    {
1097
        if ($this->isIdentifierComposite()) {
1098
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
1099
        }
1100
1101
        if ( ! isset($this->identifier[0])) {
1102
            throw MappingException::noIdDefined($this->className);
1103 8
        }
1104
1105 8
        return $this->identifier[0];
1106 8
    }
1107 8
1108
    /**
1109
     * INTERNAL:
1110
     * Sets the mapped identifier/primary key fields of this class.
1111
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1112
     *
1113
     * @param array $identifier
1114
     *
1115
     * @return void
1116
     */
1117
    public function setIdentifier(array $identifier)
1118
    {
1119
        $this->identifier = $identifier;
1120
    }
1121
1122 482
    /**
1123
     * {@inheritDoc}
1124 482
     */
1125
    public function getIdentifier()
1126
    {
1127
        return $this->identifier;
1128 482
    }
1129
1130
    /**
1131
     * {@inheritDoc}
1132
     */
1133
    public function hasField($fieldName)
1134
    {
1135
        return isset($this->declaredProperties[$fieldName])
1136
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
1137
    }
1138
1139
    /**
1140
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
1141
     *
1142
     * @param EntityManagerInterface $em
1143
     *
1144
     * @return array
1145
     */
1146
    public function getIdentifierColumns(EntityManagerInterface $em) : array
1147
    {
1148
        $columns = [];
1149 237
1150
        foreach ($this->identifier as $idProperty) {
1151 237
            $property = $this->getProperty($idProperty);
1152 237
1153 237
            if ($property instanceof FieldMetadata) {
1154
                $columns[$property->getColumnName()] = $property;
1155
1156
                continue;
1157
            }
1158
1159
            /** @var AssociationMetadata $property */
1160
1161
            // Association defined as Id field
1162
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
1163
1164
            if (! $property->isOwningSide()) {
1165
                $property    = $targetClass->getProperty($property->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1166
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
1167 4
            }
1168
1169 4
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
1170 1
                ? $property->getJoinTable()->getInverseJoinColumns()
1171
                : $property->getJoinColumns()
1172
            ;
1173 3
1174
            foreach ($joinColumns as $joinColumn) {
1175
                /** @var JoinColumnMetadata $joinColumn */
1176
                $columnName           = $joinColumn->getColumnName();
1177
                $referencedColumnName = $joinColumn->getReferencedColumnName();
1178
1179
                if (! $joinColumn->getType()) {
1180
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1181 5
                }
1182
1183 5
                $columns[$columnName] = $joinColumn;
1184
            }
1185
        }
1186
1187
        return $columns;
1188
    }
1189
1190
    /**
1191
     * Gets the name of the primary table.
1192
     *
1193
     * @return string|null
1194
     */
1195
    public function getTableName() : ?string
1196
    {
1197 15
        return $this->table->getName();
1198
    }
1199 15
1200
    /**
1201
     * Gets primary table's schema name.
1202
     *
1203 15
     * @return string|null
1204
     */
1205
    public function getSchemaName() : ?string
1206
    {
1207
        return $this->table->getSchema();
1208
    }
1209
1210
    /**
1211 2
     * Gets the table name to use for temporary identifier tables of this class.
1212
     *
1213 2
     * @return string
1214
     */
1215
    public function getTemporaryIdTableName() : string
1216
    {
1217
        $schema = null === $this->getSchemaName()
1218
            ? ''
1219
            : $this->getSchemaName() . '_'
1220
        ;
1221
1222
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
1223
        return $schema . $this->getTableName() . '_id_tmp';
1224
    }
1225
1226
    /**
1227 17
     * Sets the mapped subclasses of this class.
1228
     *
1229 17
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
1230
     *
1231
     * @param array $subclasses The names of all mapped subclasses.
1232
     *
1233 17
     * @return void
1234
     */
1235
    public function setSubclasses(array $subclasses) : void
1236
    {
1237
        foreach ($subclasses as $subclass) {
1238
            $this->subClasses[] = $subclass;
1239
        }
1240
    }
1241 6
1242
    /**
1243 6
     * @return array
1244
     */
1245
    public function getSubClasses() : array
1246
    {
1247
        return $this->subClasses;
1248
    }
1249
1250
    /**
1251
     * Sets the inheritance type used by the class and its subclasses.
1252
     *
1253
     * @param integer $type
1254
     *
1255
     * @return void
1256 317
     *
1257
     * @throws MappingException
1258 317
     */
1259
    public function setInheritanceType($type) : void
1260 317
    {
1261 304
        if ( ! $this->isInheritanceType($type)) {
1262
            throw MappingException::invalidInheritanceType($this->className, $type);
1263
        }
1264 317
1265 296
        $this->inheritanceType = $type;
0 ignored issues
show
Documentation Bug introduced by
The property $inheritanceType was declared of type string, but $type is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1266
    }
1267
1268 317
    /**
1269
     * Sets the override property mapping for an entity relationship.
1270
     *
1271 317
     * @param Property $property
1272 314
     *
1273
     * @return void
1274
     *
1275
     * @throws \RuntimeException
1276
     * @throws MappingException
1277 317
     * @throws CacheException
1278
     */
1279 317
    public function setPropertyOverride(Property $property) : void
1280 317
    {
1281 317
        $fieldName = $property->getName();
1282
1283
        if (! isset($this->declaredProperties[$fieldName])) {
1284 317
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1285 1
        }
1286
1287
        $originalProperty          = $this->getProperty($fieldName);
1288
        $originalPropertyClassName = get_class($originalProperty);
1289 316
1290 48
        // If moving from transient to persistent, assume it's a new property
1291 1
        if ($originalPropertyClassName === TransientMetadata::class) {
1292
            unset($this->declaredProperties[$fieldName]);
1293
1294 47
            $this->addProperty($property);
1295 47
1296
            return;
1297
        }
1298
1299
        // Do not allow to change property type
1300
        if ($originalPropertyClassName !== get_class($property)) {
1301 47
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1302 47
        }
1303
1304
        // Do not allow to change version property
1305
        if ($originalProperty instanceof VersionFieldMetadata) {
1306 47
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1307 24
        }
1308
1309
        unset($this->declaredProperties[$fieldName]);
1310 47
1311 3
        if ($property instanceof FieldMetadata) {
1312
            // Unset defined fieldName prior to override
1313
            unset($this->fieldNames[$originalProperty->getColumnName()]);
1314
1315
            // Revert what should not be allowed to change
1316
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1317 312
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1318
        } else if ($property instanceof AssociationMetadata) {
1319
            // Unset all defined fieldNames prior to override
1320
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1321 312
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1322
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1323
                }
1324
            }
1325
1326 312
            // Override what it should be allowed to change
1327 171
            if ($property->getInversedBy()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $property->getInversedBy() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1328
                $originalProperty->setInversedBy($property->getInversedBy());
1329
            }
1330 312
1331 3
            if ($property->getFetchMode() !== $originalProperty->getFetchMode()) {
1332
                $originalProperty->setFetchMode($property->getFetchMode());
1333
            }
1334
1335 309
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\Mapping\AssociationMetadata as the method getJoinColumns() does only exist in the following sub-classes of Doctrine\ORM\Mapping\AssociationMetadata: Doctrine\ORM\Mapping\ManyToOneAssociationMetadata, Doctrine\ORM\Mapping\OneToOneAssociationMetadata, Doctrine\ORM\Mapping\ToOneAssociationMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1336 64
                $originalProperty->setJoinColumns($property->getJoinColumns());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\Mapping\AssociationMetadata as the method getJoinColumns() does only exist in the following sub-classes of Doctrine\ORM\Mapping\AssociationMetadata: Doctrine\ORM\Mapping\ManyToOneAssociationMetadata, Doctrine\ORM\Mapping\OneToOneAssociationMetadata, Doctrine\ORM\Mapping\ToOneAssociationMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1337
            } else if ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\Mapping\AssociationMetadata as the method getJoinTable() does only exist in the following sub-classes of Doctrine\ORM\Mapping\AssociationMetadata: Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1338
                $originalProperty->setJoinTable($property->getJoinTable());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\Mapping\AssociationMetadata as the method getJoinTable() does only exist in the following sub-classes of Doctrine\ORM\Mapping\AssociationMetadata: Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1339
            }
1340 309
1341 309
            $property = $originalProperty;
1342
        }
1343 309
1344 30
        $this->addProperty($property);
0 ignored issues
show
Bug introduced by
It seems like $property defined by $originalProperty on line 1341 can be null; however, Doctrine\ORM\Mapping\ClassMetadata::addProperty() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1345
    }
1346
1347 309
    /**
1348 1
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1349
     *
1350 1
     * @return bool
1351 1
     */
1352
    public function isRootEntity()
1353
    {
1354
        return $this->className === $this->getRootClassName();
1355 308
    }
1356
1357 308
    /**
1358
     * Checks whether a mapped field is inherited from a superclass.
1359
     *
1360
     * @param string $fieldName
1361
     *
1362
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
1363
     */
1364
    public function isInheritedProperty($fieldName)
1365
    {
1366
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1367
1368
        return ! ($declaringClass->className === $this->className);
1369
    }
1370 270
1371
    /**
1372 270
     * {@inheritdoc}
1373
     */
1374 264
    public function setTable(TableMetadata $table) : void
1375 194
    {
1376
        $this->table = $table;
1377
1378 264
        if (empty($table->getName())) {
1379 260
            $table->setName($this->namingStrategy->classToTableName($this->className));
1380
        }
1381 86
    }
1382
1383 86
    /**
1384 86
     * Checks whether the given type identifies an inheritance type.
1385
     *
1386
     * @param integer $type
1387
     *
1388
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
1389 260
     */
1390
    private function isInheritanceType($type)
1391 260
    {
1392 260
        return $type == InheritanceType::NONE
1393 140
            || $type == InheritanceType::SINGLE_TABLE
1394 138
            || $type == InheritanceType::JOINED
1395 138
            || $type == InheritanceType::TABLE_PER_CLASS;
1396
    }
1397
1398 2
    /**
1399
     * @param string $columnName
1400
     *
1401
     * @return LocalColumnMetadata|null
1402 260
     */
1403
    public function getColumn(string $columnName): ?LocalColumnMetadata
1404 260
    {
1405 33
        foreach ($this->declaredProperties as $property) {
1406
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1407
                return $property;
1408 260
            }
1409
        }
1410
1411
        return null;
1412 260
    }
1413 260
1414
    /**
1415 260
     * Add a property mapping.
1416
     *
1417
     * @param Property $property
1418 260
     *
1419 2
     * @throws \RuntimeException
1420
     * @throws MappingException
1421
     * @throws CacheException
1422
     */
1423 2
    public function addProperty(Property $property)
1424 2
    {
1425
        $fieldName = $property->getName();
1426
1427
        // Check for empty field name
1428 260
        if (empty($fieldName)) {
1429
            throw MappingException::missingFieldName($this->className);
1430
        }
1431 264
1432
        $property->setDeclaringClass($this);
1433 264
1434 17
        switch (true) {
1435 13
            case ($property instanceof VersionFieldMetadata):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1436
                $this->validateAndCompleteFieldMapping($property);
1437
                $this->validateAndCompleteVersionFieldMapping($property);
1438 17
                break;
1439
1440
            case ($property instanceof FieldMetadata):
1441 264
                $this->validateAndCompleteFieldMapping($property);
1442 2
                break;
1443
1444
            case ($property instanceof OneToOneAssociationMetadata):
1445 262
                $this->validateAndCompleteAssociationMapping($property);
1446
                $this->validateAndCompleteToOneAssociationMetadata($property);
1447
                $this->validateAndCompleteOneToOneMapping($property);
1448
                break;
1449
1450
            case ($property instanceof OneToManyAssociationMetadata):
1451
                $this->validateAndCompleteAssociationMapping($property);
1452
                $this->validateAndCompleteToManyAssociationMetadata($property);
1453
                $this->validateAndCompleteOneToManyMapping($property);
1454
                break;
1455
1456
            case ($property instanceof ManyToOneAssociationMetadata):
1457
                $this->validateAndCompleteAssociationMapping($property);
1458 120
                $this->validateAndCompleteToOneAssociationMetadata($property);
1459
                $this->validateAndCompleteManyToOneMapping($property);
1460 120
                break;
1461
1462
            case ($property instanceof ManyToManyAssociationMetadata):
1463 119
                $this->validateAndCompleteAssociationMapping($property);
1464
                $this->validateAndCompleteToManyAssociationMetadata($property);
1465
                $this->validateAndCompleteManyToManyMapping($property);
1466
                break;
1467 119
1468
            // Transient properties are ignored on purpose here! =)
1469 119
        }
1470 25
1471
        $this->addDeclaredProperty($property);
1472
    }
1473 119
1474 29
    /**
1475
     * INTERNAL:
1476
     * Adds a property mapping without completing/validating it.
1477
     * This is mainly used to add inherited property mappings to derived classes.
1478
     *
1479 119
     * @param Property $property
1480
     *
1481
     * @return void
1482
     */
1483
    public function addInheritedProperty(Property $property)
1484
    {
1485
        $inheritedProperty = clone $property;
1486
        $declaringClass    = $property->getDeclaringClass();
1487
1488
        if ($inheritedProperty instanceof FieldMetadata) {
1489
            if (! $declaringClass->isMappedSuperclass) {
0 ignored issues
show
Bug introduced by
The property isMappedSuperclass does not seem to exist in Doctrine\ORM\Mapping\ComponentMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1490
                $inheritedProperty->setTableName($property->getTableName());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\ORM\Mapping\Property as the method getTableName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1491 128
            }
1492
1493 128
            $this->fieldNames[$property->getColumnName()] = $property->getName();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\ORM\Mapping\Property as the method getColumnName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1494
        } else if ($inheritedProperty instanceof AssociationMetadata) {
1495 126
            if ($declaringClass->isMappedSuperclass) {
1496
                $inheritedProperty->setSourceEntity($this->className);
1497 110
            }
1498 20
1499
            // Need to add inherited fieldNames
1500
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1501 110
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1502 110
                    /** @var JoinColumnMetadata $joinColumn */
1503
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1504 110
                }
1505 17
            }
1506
        }
1507 17
1508 17
        if (isset($this->declaredProperties[$property->getName()])) {
1509 17
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
0 ignored issues
show
Bug introduced by
It seems like $this->getProperty($property->getName()) can be null; however, duplicateProperty() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1510
        }
1511
1512
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1513
1514 110
        if ($inheritedProperty instanceof VersionFieldMetadata) {
1515 18
            $this->versionProperty = $inheritedProperty;
1516
        }
1517 18
    }
1518 18
1519 18
    /**
1520
     * INTERNAL:
1521
     * Adds a named query to this class.
1522
     *
1523
     * @param string $name
1524 110
     * @param string $query
1525
     *
1526 110
     * @return void
1527 110
     *
1528 2
     * @throws MappingException
1529
     */
1530
    public function addNamedQuery(string $name, string $query)
1531 110
    {
1532 6
        if (isset($this->namedQueries[$name])) {
1533
            throw MappingException::duplicateQueryMapping($this->className, $name);
1534
        }
1535 110
1536 26
        $this->namedQueries[$name] = $query;
1537
    }
1538
1539 110
    /**
1540 110
     * INTERNAL:
1541
     * Adds a named native query to this class.
1542
     *
1543 110
     * @param string $name
1544 110
     * @param string $query
1545 2
     * @param array  $queryMapping
1546
     *
1547
     * @return void
1548 110
     *
1549 6
     * @throws MappingException
1550
     */
1551
    public function addNamedNativeQuery(string $name, string $query, array $queryMapping)
1552 110
    {
1553 22
        if (isset($this->namedNativeQueries[$name])) {
1554
            throw MappingException::duplicateQueryMapping($this->className, $name);
1555
        }
1556 110
1557 110
        if (! isset($queryMapping['resultClass']) && ! isset($queryMapping['resultSetMapping'])) {
1558
            throw MappingException::missingQueryMapping($this->className, $name);
1559
        }
1560
1561 126
        if (isset($queryMapping['resultClass']) && $queryMapping['resultClass'] !== '__CLASS__') {
1562
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
1563 126
        }
1564 3
1565
        $this->namedNativeQueries[$name] = array_merge(['query' => $query], $queryMapping);
1566
    }
1567
1568
    /**
1569 126
     * INTERNAL:
1570
     * Adds a sql result set mapping to this class.
1571
     *
1572
     * @param array $resultMapping
1573
     *
1574
     * @return void
1575 603
     *
1576
     * @throws MappingException
1577 603
     */
1578
    public function addSqlResultSetMapping(array $resultMapping)
1579
    {
1580
        if (!isset($resultMapping['name'])) {
1581
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->className);
1582
        }
1583
1584
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
1585
            throw MappingException::duplicateResultSetMapping($this->className, $resultMapping['name']);
1586
        }
1587
1588 370
        if (isset($resultMapping['entities'])) {
1589
            foreach ($resultMapping['entities'] as $key => $entityResult) {
1590 370
                if (! isset($entityResult['entityClass'])) {
1591 1
                    throw MappingException::missingResultSetMappingEntity($this->className, $resultMapping['name']);
1592
                }
1593
1594 369
                $entityClassName = ($entityResult['entityClass'] !== '__CLASS__')
1595
                    ? $this->fullyQualifiedClassName($entityResult['entityClass'])
1596
                    : $entityResult['entityClass']
1597
                ;
1598
1599
                $resultMapping['entities'][$key]['entityClass'] = $entityClassName;
1600
1601
                if (isset($entityResult['fields'])) {
1602
                    foreach ($entityResult['fields'] as $k => $field) {
1603
                        if (! isset($field['name'])) {
1604
                            throw MappingException::missingResultSetMappingFieldName($this->className, $resultMapping['name']);
1605
                        }
1606
1607
                        if (! isset($field['column'])) {
1608
                            $fieldName = $field['name'];
1609
1610
                            if (strpos($fieldName, '.')) {
1611
                                list(, $fieldName) = explode('.', $fieldName);
1612
                            }
1613
1614
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
1615
                        }
1616
                    }
1617
                }
1618
            }
1619 108
        }
1620
1621 108
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
1622 108
    }
1623 108
1624
    /**
1625
     * Registers a custom repository class for the entity class.
1626
     *
1627
     * @param string|null $repositoryClassName The class name of the custom mapper.
1628 51
     *
1629
     * @return void
1630 51
     */
1631
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1632
    {
1633
        $this->customRepositoryClassName = $repositoryClassName;
1634
    }
1635
1636 292
    /**
1637
     * @return string|null
1638 292
     */
1639
    public function getCustomRepositoryClassName() : ?string
1640
    {
1641
        return $this->customRepositoryClassName;
1642
    }
1643
1644
    /**
1645
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1646
     *
1647
     * @param string $lifecycleEvent
1648 43
     *
1649
     * @return boolean
1650 43
     */
1651 42
    public function hasLifecycleCallbacks($lifecycleEvent)
1652
    {
1653
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1654 1
    }
1655
1656
    /**
1657
     * Gets the registered lifecycle callbacks for an event.
1658
     *
1659
     * @param string $event
1660
     *
1661
     * @return array
1662 322
     */
1663
    public function getLifecycleCallbacks($event)
1664 322
    {
1665
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : [];
1666 322
    }
1667 322
1668 318
    /**
1669
     * Adds a lifecycle callback for entities of this class.
1670 318
     *
1671
     * @param string $callback
1672
     * @param string $event
1673
     *
1674 22
     * @return void
1675 22
     */
1676
    public function addLifecycleCallback($callback, $event)
1677 22
    {
1678 22
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
1679 22
            return;
1680
        }
1681 22
1682
        $this->lifecycleCallbacks[$event][] = $callback;
1683 22
    }
1684
1685
    /**
1686
     * Sets the lifecycle callbacks for entities of this class.
1687
     * Any previously registered callbacks are overwritten.
1688 322
     *
1689
     * @param array $callbacks
1690
     *
1691
     * @return void
1692
     */
1693
    public function setLifecycleCallbacks(array $callbacks) : void
1694
    {
1695
        $this->lifecycleCallbacks = $callbacks;
1696
    }
1697
1698 397
    /**
1699
     * Adds a entity listener for entities of this class.
1700 397
     *
1701 397
     * @param string $eventName The entity lifecycle event.
1702
     * @param string $class     The listener class.
1703
     * @param string $method    The listener callback method.
1704
     *
1705
     * @return void
1706
     *
1707
     * @throws \Doctrine\ORM\Mapping\MappingException
1708 373
     */
1709
    public function addEntityListener(string $eventName, string $class, string $method) : void
1710 373
    {
1711
        $listener = [
1712
            'class'  => $class,
1713
            'method' => $method,
1714
        ];
1715
1716 1304
        if (! class_exists($class)) {
1717
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1718 1304
        }
1719
1720
        if (! method_exists($class, $method)) {
1721
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1722
        }
1723
1724
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
1725
            throw MappingException::duplicateEntityListener($class, $method, $this->className);
1726
        }
1727 1052
1728
        $this->entityListeners[$eventName][] = $listener;
1729 1052
    }
1730
1731
    /**
1732
     * Sets the discriminator column definition.
1733
     *
1734
     * @param DiscriminatorColumnMetadata $discriminatorColumn
1735
     *
1736
     * @return void
1737
     *
1738 1199
     * @throws MappingException
1739
     *
1740 1199
     * @see getDiscriminatorColumn()
1741
     */
1742
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1743
    {
1744
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1745
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1746
        }
1747
1748
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1749 260
1750
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1751 260
1752
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1753
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1754
        }
1755
1756
        $this->discriminatorColumn = $discriminatorColumn;
1757
    }
1758
1759 1063
    /**
1760
     * Sets the discriminator values used by this class.
1761 1063
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1762
     *
1763
     * @param array $map
1764
     *
1765
     * @return void
1766
     *
1767
     * @throws MappingException
1768
     */
1769 308
    public function setDiscriminatorMap(array $map) : void
1770
    {
1771 308
        foreach ($map as $value => $className) {
1772
            $this->addDiscriminatorMapClass($value, $className);
1773
        }
1774
    }
1775
1776
    /**
1777
     * Adds one entry of the discriminator map with a new class and corresponding name.
1778
     *
1779 71
     * @param string|int $name
1780
     * @param string     $className
1781 71
     *
1782
     * @return void
1783
     *
1784
     * @throws MappingException
1785
     */
1786
    public function addDiscriminatorMapClass($name, string $className) : void
1787
    {
1788
        $this->discriminatorMap[$name] = $className;
1789
1790 72
        if ($this->className === $className) {
1791
            $this->discriminatorValue = $name;
1792 72
1793
            return;
1794
        }
1795
1796
        if (! (class_exists($className) || interface_exists($className))) {
1797
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1798
        }
1799
1800
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->className can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
1801
            $this->subClasses[] = $className;
1802
        }
1803
    }
1804
1805
    /**
1806
     * @return ValueGenerationPlan
1807
     */
1808
    public function getValueGenerationPlan() : ValueGenerationPlan
1809
    {
1810 1781
        return $this->valueGenerationPlan;
1811
    }
1812 1781
1813
    /**
1814
     * @param ValueGenerationPlan $valueGenerationPlan
1815
     */
1816
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1817
    {
1818
        $this->valueGenerationPlan = $valueGenerationPlan;
1819
    }
1820 1382
1821
    /**
1822 1382
     * Checks whether the class has a named query with the given query name.
1823
     *
1824
     * @param string $queryName
1825
     *
1826
     * @return boolean
1827
     */
1828
    public function hasNamedQuery($queryName) : bool
1829
    {
1830 7
        return isset($this->namedQueries[$queryName]);
1831
    }
1832 7
1833 6
    /**
1834 7
     * Checks whether the class has a named native query with the given query name.
1835
     *
1836
     * @param string $queryName
1837
     *
1838 7
     * @return boolean
1839
     */
1840
    public function hasNamedNativeQuery($queryName) : bool
1841
    {
1842
        return isset($this->namedNativeQueries[$queryName]);
1843
    }
1844
1845
    /**
1846
     * Checks whether the class has a named native query with the given query name.
1847
     *
1848 2
     * @param string $name
1849
     *
1850 2
     * @return boolean
1851 2
     */
1852
    public function hasSqlResultSetMapping($name) : bool
1853 2
    {
1854
        return isset($this->sqlResultSetMappings[$name]);
1855
    }
1856
1857
    /**
1858
     * Marks this class as read only, no change tracking is applied to it.
1859
     *
1860
     * @return void
1861
     */
1862
    public function asReadOnly() : void
1863
    {
1864 391
        $this->readOnly = true;
1865
    }
1866 391
1867
    /**
1868 391
     * Whether this class is read only or not.
1869 75
     *
1870
     * @return bool
1871 391
     */
1872
    public function isReadOnly() : bool
1873
    {
1874
        return $this->readOnly;
1875
    }
1876
1877
    /**
1878
     * @return bool
1879
     */
1880
    public function isVersioned() : bool
1881
    {
1882 139
        return $this->versionProperty !== null;
1883
    }
1884 139
1885
    /**
1886
     * Map Embedded Class
1887
     *
1888 139
     * @param array $mapping
1889 139
     *
1890
     * @throws MappingException
1891
     * @return void
1892
     */
1893
    public function mapEmbedded(array $mapping) : void
0 ignored issues
show
Unused Code introduced by
The parameter $mapping is not used and could be removed.

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

Loading history...
1894
    {
1895
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1896
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1897
        }
1898
1899
        $this->embeddedClasses[$mapping['fieldName']] = [
1900
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1901 14
            'columnPrefix'   => $mapping['columnPrefix'],
1902
            'declaredField'  => isset($mapping['declaredField']) ? $mapping['declaredField'] : null,
1903 14
            'originalField'  => isset($mapping['originalField']) ? $mapping['originalField'] : null,
1904 1
            'declaringClass' => $this,
1905
        ];*/
1906
    }
1907 13
1908
    /**
1909 13
     * Inline the embeddable class
1910 9
     *
1911
     * @param string        $property
1912
     * @param ClassMetadata $embeddable
1913 13
     */
1914 4
    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.

This check looks from 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.

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

Loading history...
1915
    {
1916
        /*foreach ($embeddable->fieldMappings as $fieldName => $fieldMapping) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1917 13
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1918 8
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1919
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1920
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1921 13
                ? $property . '.' . $fieldMapping['declaredField']
1922 13
                : $property;
1923 13
1924 13
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1925 13
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1926
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1927 13
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1928 13
                    $property,
1929 1
                    $fieldMapping['columnName'],
1930 1
                    $this->reflectionClass->getName(),
1931 12
                    $embeddable->reflectionClass->getName()
1932
                );
1933
            }
1934 12
1935 8
            $this->mapField($fieldMapping);
1936 8
        }*/
1937 12
    }
1938
}
1939