Failed Conditions
Push — develop ( ba9041...24f682 )
by Guilherme
64:30
created

ClassMetadata::initializeReflection()   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 1
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 array<string, string>
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, $metadataBuildingContext);
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 $reflectionService
408
     *
409
     * @return void
410
     */
411
    public function wakeupReflection(ReflectionService $reflectionService) : void
412
    {
413
        // Restore ReflectionClass and properties
414
        $this->reflectionClass = $reflectionService->getClass($this->className);
415
416
        if (! $this->reflectionClass) {
417
            return;
418
        }
419
420
        $this->className = $this->reflectionClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $this->reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
421
422
        foreach ($this->declaredProperties as $property) {
423
            /** @var Property $property */
424
            $property->wakeupReflection($reflectionService);
425
        }
426
    }
427
428
    /**
429
     * Validates Identifier.
430
     *
431
     * @return void
432
     *
433
     * @throws MappingException
434
     */
435
    public function validateIdentifier() : void
436
    {
437
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
438
            return;
439
        }
440
441
        // Verify & complete identifier mapping
442
        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...
443
            throw MappingException::identifierRequired($this->className);
444
        }
445
446
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, function (Property $property) : bool {
447
            return $property instanceof FieldMetadata
448
                && $property->isPrimaryKey()
449
                && $property->hasValueGenerator();
450
        });
451
452
        if ($this->isIdentifierComposite() && count($explicitlyGeneratedProperties) !== 0) {
453
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
454
        }
455
    }
456
457
    /**
458
     * Validates association targets actually exist.
459
     *
460
     * @return void
461
     *
462
     * @throws MappingException
463
     */
464
    public function validateAssociations() : void
465
    {
466
        array_map(
467
            function (Property $property) {
468
                if (! ($property instanceof AssociationMetadata)) {
469
                    return;
470
                }
471
472
                $targetEntity = $property->getTargetEntity();
473
474
                if (! class_exists($targetEntity)) {
475
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
476
                }
477
            },
478
            $this->declaredProperties
479
        );
480
    }
481
482
    /**
483
     * Validates lifecycle callbacks.
484
     *
485
     * @param ReflectionService $reflectionService
486
     *
487
     * @return void
488
     *
489
     * @throws MappingException
490
     */
491
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
492
    {
493
        foreach ($this->lifecycleCallbacks as $callbacks) {
494
            /** @var array $callbacks */
495
            foreach ($callbacks as $callbackFuncName) {
496
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
497
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
498
                }
499
            }
500
        }
501
    }
502
503
    /**
504
     * Sets the change tracking policy used by this class.
505
     *
506
     * @param string $policy
507
     *
508
     * @return void
509
     */
510
    public function setChangeTrackingPolicy(string $policy) : void
511
    {
512
        $this->changeTrackingPolicy = $policy;
513
    }
514
515
    /**
516
     * Checks whether a field is part of the identifier/primary key field(s).
517
     *
518
     * @param string $fieldName The field name.
519
     *
520
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
521
     */
522
    public function isIdentifier(string $fieldName) : bool
523
    {
524
        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...
525
            return false;
526
        }
527
528
        if (! $this->isIdentifierComposite()) {
529
            return $fieldName === $this->identifier[0];
530
        }
531
532
        return in_array($fieldName, $this->identifier, true);
533
    }
534
535
    /**
536
     * @return bool
537
     */
538
    public function isIdentifierComposite() : bool
539
    {
540
        return count($this->identifier) > 1;
541
    }
542
543
    /**
544
     * Gets the named query.
545
     *
546
     * @see ClassMetadata::$namedQueries
547
     *
548
     * @param string $queryName The query name.
549
     *
550
     * @return string
551
     *
552
     * @throws MappingException
553
     */
554
    public function getNamedQuery($queryName) : string
555
    {
556
        if (! isset($this->namedQueries[$queryName])) {
557
            throw MappingException::queryNotFound($this->className, $queryName);
558
        }
559
560
        return $this->namedQueries[$queryName];
561
    }
562
563
    /**
564
     * Gets all named queries of the class.
565
     *
566
     * @return array
567
     */
568
    public function getNamedQueries() : array
569
    {
570
        return $this->namedQueries;
571
    }
572
573
    /**
574
     * Gets the named native query.
575
     *
576
     * @see ClassMetadata::$namedNativeQueries
577
     *
578
     * @param string $queryName The query name.
579
     *
580
     * @return array
581
     *
582
     * @throws MappingException
583
     */
584
    public function getNamedNativeQuery($queryName) : array
585
    {
586
        if ( ! isset($this->namedNativeQueries[$queryName])) {
587 578
            throw MappingException::queryNotFound($this->className, $queryName);
588
        }
589 578
590 578
        return $this->namedNativeQueries[$queryName];
591 578
    }
592 578
593 578
    /**
594
     * Gets all named native queries of the class.
595
     *
596
     * @return array
597
     */
598
    public function getNamedNativeQueries() : array
599
    {
600 224
        return $this->namedNativeQueries;
601
    }
602 224
603
    /**
604
     * Gets the result set mapping.
605
     *
606
     * @see ClassMetadata::$sqlResultSetMappings
607
     *
608
     * @param string $name The result set mapping name.
609
     *
610
     * @return array
611
     *
612 1
     * @throws MappingException
613
     */
614 1
    public function getSqlResultSetMapping($name)
615
    {
616
        if (! isset($this->sqlResultSetMappings[$name])) {
617
            throw MappingException::resultMappingNotFound($this->className, $name);
618
        }
619
620
        return $this->sqlResultSetMappings[$name];
621
    }
622
623
    /**
624
     * Gets all sql result set mappings of the class.
625
     *
626
     * @return array
627
     */
628
    public function getSqlResultSetMappings()
629
    {
630
        return $this->sqlResultSetMappings;
631
    }
632
633
    /**
634
     * Validates & completes the basic mapping information for field mapping.
635
     *
636
     * @param FieldMetadata $property
637
     *
638
     * @throws MappingException If something is wrong with the mapping.
639
     */
640
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
641
    {
642
        $fieldName  = $property->getName();
643 466
        $columnName = $property->getColumnName();
644
645 466
        if (empty($columnName)) {
646 91
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
647
648 91
            $property->setColumnName($columnName);
649 91
        }
650
651 91
        if (! $this->isMappedSuperclass) {
652 91
            $property->setTableName($this->getTableName());
653
        }
654
655
        // Check for already declared column
656 91
        if (isset($this->fieldNames[$columnName]) ||
657
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
658
            throw MappingException::duplicateColumnName($this->className, $columnName);
659 446
        }
660 446
661
        // Complete id mapping
662 446
        if ($property->isPrimaryKey()) {
663 27
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
664
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
665
            }
666 423
667
            if ($property->getType()->canRequireSQLConversion()) {
668
                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...
669
            };
670
671
            if (! in_array($fieldName, $this->identifier)) {
672
                $this->identifier[] = $fieldName;
673
            }
674
        }
675
676
        $this->fieldNames[$columnName] = $fieldName;
677 6
    }
678
679 6
    /**
680 6
     * Validates & completes the basic mapping information for field mapping.
681
     *
682 6
     * @param VersionFieldMetadata $property
683
     *
684
     * @throws MappingException If something is wrong with the mapping.
685
     */
686
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
687
    {
688
        $this->versionProperty = $property;
689
690
        $options = $property->getOptions();
691
692
        if (isset($options['default'])) {
693 24
            return;
694
        }
695 24
696 24
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'])) {
697
            $property->setOptions(array_merge($options, ['default' => 1]));
698
699
            return;
700
        }
701
702
        if ($property->getTypeName() === 'datetime') {
703
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
704
705
            return;
706 312
        }
707
708 312
        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...
709
    }
710
711
    /**
712
     * Validates & completes the basic mapping information that is common to all
713
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
714
     *
715
     * @param AssociationMetadata $property
716
     *
717
     * @throws MappingException If something is wrong with the mapping.
718
     * @throws CacheException   If entity is not cacheable.
719
     */
720
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
721
    {
722
        $fieldName    = $property->getName();
723
        $targetEntity = $property->getTargetEntity();
724
725
        if (! $targetEntity) {
726
            throw MappingException::missingTargetEntity($fieldName);
727
        }
728
729
        $property->setSourceEntity($this->className);
730
        $property->setOwningSide($property->getMappedBy() === null);
731
        $property->setTargetEntity($targetEntity);
732
733
        // Mandatory and optional attributes for either side
734
        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...
735
            $property->setOwningSide(false);
736 7
        }
737
738
        // Complete id mapping
739
        if ($property->isPrimaryKey()) {
740 7
            if ($property->isOrphanRemoval()) {
741
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
742
            }
743
744
            if ( ! in_array($property->getName(), $this->identifier)) {
745
                if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
746
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
747
                        $property->getTargetEntity(),
748
                        $this->className,
749
                        $fieldName
750
                    );
751
                }
752
753 7
                $this->identifier[] = $property->getName();
754
            }
755
756
            if ($this->cache && !$property->getCache()) {
757 7
                throw CacheException::nonCacheableEntityAssociation($this->className, $fieldName);
758 1
            }
759
760
            if ($property instanceof ToManyAssociationMetadata) {
761 7
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
762 1
            }
763 1
        }
764 1
765 1
        // Cascades
766 1
        $cascadeTypes = ['remove', 'persist', 'refresh', 'merge', 'detach'];
767 1
        $cascades     = array_map('strtolower', $property->getCascade());
768
769
        if (in_array('all', $cascades)) {
770 7
            $cascades = $cascadeTypes;
771 1
        }
772 1
773
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
774
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
775
776
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
777 7
        }
778 1
779
        $property->setCascade($cascades);
780
    }
781 7
782 1
    /**
783
     * Validates & completes a to-one association mapping.
784
     *
785 7
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
786
     *
787
     * @throws \RuntimeException
788
     * @throws MappingException
789 7
     */
790
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
791
    {
792
        $fieldName = $property->getName();
793 7
794
        if ($property->getJoinColumns()) {
795
            $property->setOwningSide(true);
796
        }
797 7
798 1
        if ($property->isOwningSide()) {
799
            if (empty($property->getJoinColumns())) {
800
                // Apply default join column
801 7
                $property->addJoinColumn(new JoinColumnMetadata());
802 1
            }
803
804
            $uniqueConstraintColumns = [];
805 7
806
            foreach ($property->getJoinColumns() as $joinColumn) {
807
                /** @var JoinColumnMetadata $joinColumn */
808
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
809 7
                    if (1 === count($property->getJoinColumns())) {
810
                        if (! $property->isPrimaryKey()) {
811
                            $joinColumn->setUnique(true);
812
                        }
813 7
                    } else {
814 1
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
815
                    }
816
                }
817 7
818
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
819
820
                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...
821 7
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
822
                }
823
824
                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...
825 7
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
826
                }
827
828
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
829
            }
830
831
            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...
832
                if ( ! $this->table) {
833 671
                    throw new \RuntimeException(
834
                        "ClassMetadata::setTable() has to be called before defining a one to one relationship."
835 671
                    );
836
                }
837
838
                $this->table->addUniqueConstraint(
839
                    [
840
                        'name'    => sprintf('%s_uniq', $fieldName),
841
                        'columns' => $uniqueConstraintColumns,
842
                        'options' => [],
843
                        'flags'   => [],
844
                    ]
845 1988
                );
846
            }
847
        }
848 1988
849 1988
        if ($property->isOrphanRemoval()) {
850
            $cascades = $property->getCascade();
851 1988
852
            if (! in_array('remove', $cascades)) {
853
                $cascades[] = 'remove';
854
855
                $property->setCascade($cascades);
856
            }
857
858
            // @todo guilhermeblanco where is this used?
859
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
860
            //$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...
861
        }
862
863
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
864
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
865
        }
866
    }
867
868
    /**
869
     * Validates & completes a to-many association mapping.
870
     *
871 1988
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
872
     *
873
     * @throws MappingException
874
     */
875
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
876
    {
877
        if ($property->isPrimaryKey()) {
878
            throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
879
        }
880
    }
881 1984
882
    /**
883 1984
     * Validates & completes a one-to-one association mapping.
884
     *
885
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
886 1988
     */
887 1679
    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...
888
    {
889 1988
        // Do nothing
890
    }
891
892
    /**
893
     * Validates & completes a many-to-one association mapping.
894
     *
895
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
896
     *
897
     * @throws MappingException
898
     */
899 543
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
900
    {
901 543
        // A many-to-one mapping is essentially a one-one backreference
902
        if ($property->isOrphanRemoval()) {
903 543
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
904 539
        }
905
    }
906
907 543
    /**
908 543
     * Validates & completes a one-to-many association mapping.
909
     *
910 543
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
911
     *
912
     * @throws MappingException
913
     */
914
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
915
    {
916
        // OneToMany MUST be inverse side
917
        $property->setOwningSide(false);
918
919 385
        // OneToMany MUST have mappedBy
920
        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...
921 385
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
922 44
        }
923
924
        if ($property->isOrphanRemoval()) {
925
            $cascades = $property->getCascade();
926 381
927 8
            if (! in_array('remove', $cascades)) {
928
                $cascades[] = 'remove';
929
930 373
                $property->setCascade($cascades);
931
            }
932
        }
933 373
    }
934
935
    /**
936
     * Validates & completes a many-to-many association mapping.
937
     *
938
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
939
     *
940
     * @throws MappingException
941
     */
942 382
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
943
    {
944 382
        if ($property->isOwningSide()) {
945 254
            // owning side MUST have a join table
946 254
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
947
948
            $property->setJoinTable($joinTable);
949 381
950
            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...
951
                $joinTableName = $this->namingStrategy->joinTableName(
952
                    $property->getSourceEntity(),
953
                    $property->getTargetEntity(),
954
                    $property->getName()
955
                );
956
957
                $joinTable->setName($joinTableName);
958
            }
959
960 382
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() == $property->getTargetEntity() && ! $joinTable->hasColumns();
961
962 382
            if (! $joinTable->getJoinColumns()) {
963 13
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
964 13
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
965 13
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
966
                $joinColumn           = new JoinColumnMetadata();
967
968
                $joinColumn->setColumnName($columnName);
969 381
                $joinColumn->setReferencedColumnName($referencedColumnName);
970
                $joinColumn->setOnDelete('CASCADE');
971
972
                $joinTable->addJoinColumn($joinColumn);
973
            }
974 538
975
            if (! $joinTable->getInverseJoinColumns()) {
976 538
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
977
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
978
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
979
                $joinColumn           = new JoinColumnMetadata();
980
981
                $joinColumn->setColumnName($columnName);
982
                $joinColumn->setReferencedColumnName($referencedColumnName);
983
                $joinColumn->setOnDelete('CASCADE');
984 19
985
                $joinTable->addInverseJoinColumn($joinColumn);
986 19
            }
987
988
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
989
                /** @var JoinColumnMetadata $joinColumn */
990 19
                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...
991 5
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
992
                }
993
994 19
                $referencedColumnName = $joinColumn->getReferencedColumnName();
995 19
996
                if (! $joinColumn->getColumnName()) {
997
                    $columnName = $this->namingStrategy->joinKeyColumnName(
998
                        $property->getSourceEntity(),
999
                        $referencedColumnName
1000
                    );
1001
1002
                    $joinColumn->setColumnName($columnName);
1003 2
                }
1004
            }
1005 2
1006 2
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
1007
                /** @var JoinColumnMetadata $inverseJoinColumn */
1008
                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...
1009
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
1010
                }
1011
1012
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
1013
1014 15
                if (! $inverseJoinColumn->getColumnName()) {
1015
                    $columnName = $this->namingStrategy->joinKeyColumnName(
1016 15
                        $property->getTargetEntity(),
1017 1
                        $referencedColumnName
1018 1
                    );
1019
1020
                    $inverseJoinColumn->setColumnName($columnName);
1021
                }
1022 15
            }
1023 15
        }
1024
    }
1025
1026 15
    /**
1027
     * {@inheritDoc}
1028
     */
1029
    public function getIdentifierFieldNames()
1030
    {
1031
        return $this->identifier;
1032
    }
1033
1034
    /**
1035
     * Gets the name of the single id field. Note that this only works on
1036 123
     * entity classes that have a single-field pk.
1037
     *
1038 123
     * @return string
1039 123
     *
1040
     * @throws MappingException If the class has a composite primary key.
1041
     */
1042
    public function getSingleIdentifierFieldName()
1043
    {
1044
        if ($this->isIdentifierComposite()) {
1045
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
1046 267
        }
1047
1048 267
        if ( ! isset($this->identifier[0])) {
1049
            throw MappingException::noIdDefined($this->className);
1050
        }
1051
1052
        return $this->identifier[0];
1053
    }
1054
1055
    /**
1056 459
     * INTERNAL:
1057
     * Sets the mapped identifier/primary key fields of this class.
1058 459
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1059
     *
1060
     * @param array $identifier
1061
     *
1062
     * @return void
1063
     */
1064
    public function setIdentifier(array $identifier)
1065
    {
1066 290
        $this->identifier = $identifier;
1067
    }
1068 290
1069
    /**
1070
     * {@inheritDoc}
1071
     */
1072
    public function getIdentifier()
1073
    {
1074
        return $this->identifier;
1075
    }
1076
1077
    /**
1078
     * {@inheritDoc}
1079 1032
     */
1080
    public function hasField($fieldName)
1081 1032
    {
1082 1
        return isset($this->declaredProperties[$fieldName])
1083
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
1084
    }
1085 1031
1086 1026
    /**
1087
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
1088
     *
1089 91
     * @param EntityManagerInterface $em
1090
     *
1091
     * @return array
1092
     */
1093
    public function getIdentifierColumns(EntityManagerInterface $em) : array
1094
    {
1095
        $columns = [];
1096
1097
        foreach ($this->identifier as $idProperty) {
1098
            $property = $this->getProperty($idProperty);
1099
1100
            if ($property instanceof FieldMetadata) {
1101
                $columns[$property->getColumnName()] = $property;
1102
1103 8
                continue;
1104
            }
1105 8
1106 8
            /** @var AssociationMetadata $property */
1107 8
1108
            // Association defined as Id field
1109
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
1110
1111
            if (! $property->isOwningSide()) {
1112
                $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...
1113
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
1114
            }
1115
1116
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
1117
                ? $property->getJoinTable()->getInverseJoinColumns()
1118
                : $property->getJoinColumns()
1119
            ;
1120
1121
            foreach ($joinColumns as $joinColumn) {
1122 482
                /** @var JoinColumnMetadata $joinColumn */
1123
                $columnName           = $joinColumn->getColumnName();
1124 482
                $referencedColumnName = $joinColumn->getReferencedColumnName();
1125
1126
                if (! $joinColumn->getType()) {
1127
                    $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...
1128 482
                }
1129
1130
                $columns[$columnName] = $joinColumn;
1131
            }
1132
        }
1133
1134
        return $columns;
1135
    }
1136
1137
    /**
1138
     * Gets the name of the primary table.
1139
     *
1140
     * @return string|null
1141
     */
1142
    public function getTableName() : ?string
1143
    {
1144
        return $this->table->getName();
1145
    }
1146
1147
    /**
1148
     * Gets primary table's schema name.
1149 237
     *
1150
     * @return string|null
1151 237
     */
1152 237
    public function getSchemaName() : ?string
1153 237
    {
1154
        return $this->table->getSchema();
1155
    }
1156
1157
    /**
1158
     * Gets the table name to use for temporary identifier tables of this class.
1159
     *
1160
     * @return string
1161
     */
1162
    public function getTemporaryIdTableName() : string
1163
    {
1164
        $schema = null === $this->getSchemaName()
1165
            ? ''
1166
            : $this->getSchemaName() . '_'
1167 4
        ;
1168
1169 4
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
1170 1
        return $schema . $this->getTableName() . '_id_tmp';
1171
    }
1172
1173 3
    /**
1174
     * Sets the mapped subclasses of this class.
1175
     *
1176
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
1177
     *
1178
     * @param array $subclasses The names of all mapped subclasses.
1179
     *
1180
     * @return void
1181 5
     */
1182
    public function setSubclasses(array $subclasses) : void
1183 5
    {
1184
        foreach ($subclasses as $subclass) {
1185
            $this->subClasses[] = $subclass;
1186
        }
1187
    }
1188
1189
    /**
1190
     * @return array
1191
     */
1192
    public function getSubClasses() : array
1193
    {
1194
        return $this->subClasses;
1195
    }
1196
1197 15
    /**
1198
     * Sets the inheritance type used by the class and its subclasses.
1199 15
     *
1200
     * @param integer $type
1201
     *
1202
     * @return void
1203 15
     *
1204
     * @throws MappingException
1205
     */
1206
    public function setInheritanceType($type) : void
1207
    {
1208
        if ( ! $this->isInheritanceType($type)) {
1209
            throw MappingException::invalidInheritanceType($this->className, $type);
1210
        }
1211 2
1212
        $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...
1213 2
    }
1214
1215
    /**
1216
     * Sets the override property mapping for an entity relationship.
1217
     *
1218
     * @param Property $property
1219
     *
1220
     * @return void
1221
     *
1222
     * @throws \RuntimeException
1223
     * @throws MappingException
1224
     * @throws CacheException
1225
     */
1226
    public function setPropertyOverride(Property $property) : void
1227 17
    {
1228
        $fieldName = $property->getName();
1229 17
1230
        if (! isset($this->declaredProperties[$fieldName])) {
1231
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1232
        }
1233 17
1234
        $originalProperty          = $this->getProperty($fieldName);
1235
        $originalPropertyClassName = get_class($originalProperty);
1236
1237
        // If moving from transient to persistent, assume it's a new property
1238
        if ($originalPropertyClassName === TransientMetadata::class) {
1239
            unset($this->declaredProperties[$fieldName]);
1240
1241 6
            $this->addProperty($property);
1242
1243 6
            return;
1244
        }
1245
1246
        // Do not allow to change property type
1247
        if ($originalPropertyClassName !== get_class($property)) {
1248
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1249
        }
1250
1251
        // Do not allow to change version property
1252
        if ($originalProperty instanceof VersionFieldMetadata) {
1253
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1254
        }
1255
1256 317
        unset($this->declaredProperties[$fieldName]);
1257
1258 317
        if ($property instanceof FieldMetadata) {
1259
            // Unset defined fieldName prior to override
1260 317
            unset($this->fieldNames[$originalProperty->getColumnName()]);
1261 304
1262
            // Revert what should not be allowed to change
1263
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1264 317
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1265 296
        } else if ($property instanceof AssociationMetadata) {
1266
            // Unset all defined fieldNames prior to override
1267
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1268 317
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1269
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1270
                }
1271 317
            }
1272 314
1273
            // Override what it should be allowed to change
1274
            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...
1275
                $originalProperty->setInversedBy($property->getInversedBy());
1276
            }
1277 317
1278
            if ($property->getFetchMode() !== $originalProperty->getFetchMode()) {
1279 317
                $originalProperty->setFetchMode($property->getFetchMode());
1280 317
            }
1281 317
1282
            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...
1283
                $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...
1284 317
            } 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...
1285 1
                $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...
1286
            }
1287
1288
            $property = $originalProperty;
1289 316
        }
1290 48
1291 1
        $this->addProperty($property);
0 ignored issues
show
Bug introduced by
It seems like $property defined by $originalProperty on line 1288 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...
1292
    }
1293
1294 47
    /**
1295 47
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1296
     *
1297
     * @return bool
1298
     */
1299
    public function isRootEntity()
1300
    {
1301 47
        return $this->className === $this->getRootClassName();
1302 47
    }
1303
1304
    /**
1305
     * Checks whether a mapped field is inherited from a superclass.
1306 47
     *
1307 24
     * @param string $fieldName
1308
     *
1309
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
1310 47
     */
1311 3
    public function isInheritedProperty($fieldName)
1312
    {
1313
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1314
1315
        return ! ($declaringClass->className === $this->className);
1316
    }
1317 312
1318
    /**
1319
     * {@inheritdoc}
1320
     */
1321 312
    public function setTable(TableMetadata $table) : void
1322
    {
1323
        $this->table = $table;
1324
1325
        if (empty($table->getName())) {
1326 312
            $table->setName($this->namingStrategy->classToTableName($this->className));
1327 171
        }
1328
    }
1329
1330 312
    /**
1331 3
     * Checks whether the given type identifies an inheritance type.
1332
     *
1333
     * @param integer $type
1334
     *
1335 309
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
1336 64
     */
1337
    private function isInheritanceType($type)
1338
    {
1339
        return $type == InheritanceType::NONE
1340 309
            || $type == InheritanceType::SINGLE_TABLE
1341 309
            || $type == InheritanceType::JOINED
1342
            || $type == InheritanceType::TABLE_PER_CLASS;
1343 309
    }
1344 30
1345
    /**
1346
     * @param string $columnName
1347 309
     *
1348 1
     * @return LocalColumnMetadata|null
1349
     */
1350 1
    public function getColumn(string $columnName): ?LocalColumnMetadata
1351 1
    {
1352
        foreach ($this->declaredProperties as $property) {
1353
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1354
                return $property;
1355 308
            }
1356
        }
1357 308
1358
        return null;
1359
    }
1360
1361
    /**
1362
     * Add a property mapping.
1363
     *
1364
     * @param Property $property
1365
     *
1366
     * @throws \RuntimeException
1367
     * @throws MappingException
1368
     * @throws CacheException
1369
     */
1370 270
    public function addProperty(Property $property)
1371
    {
1372 270
        $fieldName = $property->getName();
1373
1374 264
        // Check for empty field name
1375 194
        if (empty($fieldName)) {
1376
            throw MappingException::missingFieldName($this->className);
1377
        }
1378 264
1379 260
        $property->setDeclaringClass($this);
1380
1381 86
        switch (true) {
1382
            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...
1383 86
                $this->validateAndCompleteFieldMapping($property);
1384 86
                $this->validateAndCompleteVersionFieldMapping($property);
1385
                break;
1386
1387
            case ($property instanceof FieldMetadata):
1388
                $this->validateAndCompleteFieldMapping($property);
1389 260
                break;
1390
1391 260
            case ($property instanceof OneToOneAssociationMetadata):
1392 260
                $this->validateAndCompleteAssociationMapping($property);
1393 140
                $this->validateAndCompleteToOneAssociationMetadata($property);
1394 138
                $this->validateAndCompleteOneToOneMapping($property);
1395 138
                break;
1396
1397
            case ($property instanceof OneToManyAssociationMetadata):
1398 2
                $this->validateAndCompleteAssociationMapping($property);
1399
                $this->validateAndCompleteToManyAssociationMetadata($property);
1400
                $this->validateAndCompleteOneToManyMapping($property);
1401
                break;
1402 260
1403
            case ($property instanceof ManyToOneAssociationMetadata):
1404 260
                $this->validateAndCompleteAssociationMapping($property);
1405 33
                $this->validateAndCompleteToOneAssociationMetadata($property);
1406
                $this->validateAndCompleteManyToOneMapping($property);
1407
                break;
1408 260
1409
            case ($property instanceof ManyToManyAssociationMetadata):
1410
                $this->validateAndCompleteAssociationMapping($property);
1411
                $this->validateAndCompleteToManyAssociationMetadata($property);
1412 260
                $this->validateAndCompleteManyToManyMapping($property);
1413 260
                break;
1414
1415 260
            default:
1416
                // Transient properties are ignored on purpose here! =)
1417
                break;
1418 260
        }
1419 2
1420
        $this->addDeclaredProperty($property);
1421
    }
1422
1423 2
    /**
1424 2
     * INTERNAL:
1425
     * Adds a property mapping without completing/validating it.
1426
     * This is mainly used to add inherited property mappings to derived classes.
1427
     *
1428 260
     * @param Property $property
1429
     *
1430
     * @return void
1431 264
     */
1432
    public function addInheritedProperty(Property $property)
1433 264
    {
1434 17
        $inheritedProperty = clone $property;
1435 13
        $declaringClass    = $property->getDeclaringClass();
1436
1437
        if ($inheritedProperty instanceof FieldMetadata) {
1438 17
            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...
1439
                $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...
1440
            }
1441 264
1442 2
            $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...
1443
        } else if ($inheritedProperty instanceof AssociationMetadata) {
1444
            if ($declaringClass->isMappedSuperclass) {
1445 262
                $inheritedProperty->setSourceEntity($this->className);
1446
            }
1447
1448
            // Need to add inherited fieldNames
1449
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1450
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1451
                    /** @var JoinColumnMetadata $joinColumn */
1452
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1453
                }
1454
            }
1455
        }
1456
1457
        if (isset($this->declaredProperties[$property->getName()])) {
1458 120
            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...
1459
        }
1460 120
1461
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1462
1463 119
        if ($inheritedProperty instanceof VersionFieldMetadata) {
1464
            $this->versionProperty = $inheritedProperty;
1465
        }
1466
    }
1467 119
1468
    /**
1469 119
     * INTERNAL:
1470 25
     * Adds a named query to this class.
1471
     *
1472
     * @param string $name
1473 119
     * @param string $query
1474 29
     *
1475
     * @return void
1476
     *
1477
     * @throws MappingException
1478
     */
1479 119
    public function addNamedQuery(string $name, string $query)
1480
    {
1481
        if (isset($this->namedQueries[$name])) {
1482
            throw MappingException::duplicateQueryMapping($this->className, $name);
1483
        }
1484
1485
        $this->namedQueries[$name] = $query;
1486
    }
1487
1488
    /**
1489
     * INTERNAL:
1490
     * Adds a named native query to this class.
1491 128
     *
1492
     * @param string $name
1493 128
     * @param string $query
1494
     * @param array  $queryMapping
1495 126
     *
1496
     * @return void
1497 110
     *
1498 20
     * @throws MappingException
1499
     */
1500
    public function addNamedNativeQuery(string $name, string $query, array $queryMapping)
1501 110
    {
1502 110
        if (isset($this->namedNativeQueries[$name])) {
1503
            throw MappingException::duplicateQueryMapping($this->className, $name);
1504 110
        }
1505 17
1506
        if (! isset($queryMapping['resultClass']) && ! isset($queryMapping['resultSetMapping'])) {
1507 17
            throw MappingException::missingQueryMapping($this->className, $name);
1508 17
        }
1509 17
1510
        if (isset($queryMapping['resultClass']) && $queryMapping['resultClass'] !== '__CLASS__') {
1511
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
1512
        }
1513
1514 110
        $this->namedNativeQueries[$name] = array_merge(['query' => $query], $queryMapping);
1515 18
    }
1516
1517 18
    /**
1518 18
     * INTERNAL:
1519 18
     * Adds a sql result set mapping to this class.
1520
     *
1521
     * @param array $resultMapping
1522
     *
1523
     * @return void
1524 110
     *
1525
     * @throws MappingException
1526 110
     */
1527 110
    public function addSqlResultSetMapping(array $resultMapping)
1528 2
    {
1529
        if (!isset($resultMapping['name'])) {
1530
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->className);
1531 110
        }
1532 6
1533
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
1534
            throw MappingException::duplicateResultSetMapping($this->className, $resultMapping['name']);
1535 110
        }
1536 26
1537
        if (isset($resultMapping['entities'])) {
1538
            foreach ($resultMapping['entities'] as $key => $entityResult) {
1539 110
                if (! isset($entityResult['entityClass'])) {
1540 110
                    throw MappingException::missingResultSetMappingEntity($this->className, $resultMapping['name']);
1541
                }
1542
1543 110
                $entityClassName = ($entityResult['entityClass'] !== '__CLASS__')
1544 110
                    ? $this->fullyQualifiedClassName($entityResult['entityClass'])
1545 2
                    : $entityResult['entityClass']
1546
                ;
1547
1548 110
                $resultMapping['entities'][$key]['entityClass'] = $entityClassName;
1549 6
1550
                if (isset($entityResult['fields'])) {
1551
                    foreach ($entityResult['fields'] as $k => $field) {
1552 110
                        if (! isset($field['name'])) {
1553 22
                            throw MappingException::missingResultSetMappingFieldName($this->className, $resultMapping['name']);
1554
                        }
1555
1556 110
                        if (! isset($field['column'])) {
1557 110
                            $fieldName = $field['name'];
1558
1559
                            if (strpos($fieldName, '.')) {
1560
                                list(, $fieldName) = explode('.', $fieldName);
1561 126
                            }
1562
1563 126
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
1564 3
                        }
1565
                    }
1566
                }
1567
            }
1568
        }
1569 126
1570
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
1571
    }
1572
1573
    /**
1574
     * Registers a custom repository class for the entity class.
1575 603
     *
1576
     * @param string|null $repositoryClassName The class name of the custom mapper.
1577 603
     *
1578
     * @return void
1579
     */
1580
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1581
    {
1582
        $this->customRepositoryClassName = $repositoryClassName;
1583
    }
1584
1585
    /**
1586
     * @return string|null
1587
     */
1588 370
    public function getCustomRepositoryClassName() : ?string
1589
    {
1590 370
        return $this->customRepositoryClassName;
1591 1
    }
1592
1593
    /**
1594 369
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1595
     *
1596
     * @param string $lifecycleEvent
1597
     *
1598
     * @return boolean
1599
     */
1600
    public function hasLifecycleCallbacks($lifecycleEvent)
1601
    {
1602
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1603
    }
1604
1605
    /**
1606
     * Gets the registered lifecycle callbacks for an event.
1607
     *
1608
     * @param string $event
1609
     *
1610
     * @return array
1611
     */
1612
    public function getLifecycleCallbacks($event)
1613
    {
1614
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : [];
1615
    }
1616
1617
    /**
1618
     * Adds a lifecycle callback for entities of this class.
1619 108
     *
1620
     * @param string $callback
1621 108
     * @param string $event
1622 108
     *
1623 108
     * @return void
1624
     */
1625
    public function addLifecycleCallback($callback, $event)
1626
    {
1627
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
1628 51
            return;
1629
        }
1630 51
1631
        $this->lifecycleCallbacks[$event][] = $callback;
1632
    }
1633
1634
    /**
1635
     * Sets the lifecycle callbacks for entities of this class.
1636 292
     * Any previously registered callbacks are overwritten.
1637
     *
1638 292
     * @param array $callbacks
1639
     *
1640
     * @return void
1641
     */
1642
    public function setLifecycleCallbacks(array $callbacks) : void
1643
    {
1644
        $this->lifecycleCallbacks = $callbacks;
1645
    }
1646
1647
    /**
1648 43
     * Adds a entity listener for entities of this class.
1649
     *
1650 43
     * @param string $eventName The entity lifecycle event.
1651 42
     * @param string $class     The listener class.
1652
     * @param string $method    The listener callback method.
1653
     *
1654 1
     * @return void
1655
     *
1656
     * @throws \Doctrine\ORM\Mapping\MappingException
1657
     */
1658
    public function addEntityListener(string $eventName, string $class, string $method) : void
1659
    {
1660
        $listener = [
1661
            'class'  => $class,
1662 322
            'method' => $method,
1663
        ];
1664 322
1665
        if (! class_exists($class)) {
1666 322
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1667 322
        }
1668 318
1669
        if (! method_exists($class, $method)) {
1670 318
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1671
        }
1672
1673
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
1674 22
            throw MappingException::duplicateEntityListener($class, $method, $this->className);
1675 22
        }
1676
1677 22
        $this->entityListeners[$eventName][] = $listener;
1678 22
    }
1679 22
1680
    /**
1681 22
     * Sets the discriminator column definition.
1682
     *
1683 22
     * @param DiscriminatorColumnMetadata $discriminatorColumn
1684
     *
1685
     * @return void
1686
     *
1687
     * @throws MappingException
1688 322
     *
1689
     * @see getDiscriminatorColumn()
1690
     */
1691
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1692
    {
1693
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1694
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1695
        }
1696
1697
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1698 397
1699
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1700 397
1701 397
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1702
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1703
        }
1704
1705
        $this->discriminatorColumn = $discriminatorColumn;
1706
    }
1707
1708 373
    /**
1709
     * Sets the discriminator values used by this class.
1710 373
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1711
     *
1712
     * @param array $map
1713
     *
1714
     * @return void
1715
     *
1716 1304
     * @throws MappingException
1717
     */
1718 1304
    public function setDiscriminatorMap(array $map) : void
1719
    {
1720
        foreach ($map as $value => $className) {
1721
            $this->addDiscriminatorMapClass($value, $className);
1722
        }
1723
    }
1724
1725
    /**
1726
     * Adds one entry of the discriminator map with a new class and corresponding name.
1727 1052
     *
1728
     * @param string|int $name
1729 1052
     * @param string     $className
1730
     *
1731
     * @return void
1732
     *
1733
     * @throws MappingException
1734
     */
1735
    public function addDiscriminatorMapClass($name, string $className) : void
1736
    {
1737
        $this->discriminatorMap[$name] = $className;
1738 1199
1739
        if ($this->className === $className) {
1740 1199
            $this->discriminatorValue = $name;
1741
1742
            return;
1743
        }
1744
1745
        if (! (class_exists($className) || interface_exists($className))) {
1746
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1747
        }
1748
1749 260
        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...
1750
            $this->subClasses[] = $className;
1751 260
        }
1752
    }
1753
1754
    /**
1755
     * @return ValueGenerationPlan
1756
     */
1757
    public function getValueGenerationPlan() : ValueGenerationPlan
1758
    {
1759 1063
        return $this->valueGenerationPlan;
1760
    }
1761 1063
1762
    /**
1763
     * @param ValueGenerationPlan $valueGenerationPlan
1764
     */
1765
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1766
    {
1767
        $this->valueGenerationPlan = $valueGenerationPlan;
1768
    }
1769 308
1770
    /**
1771 308
     * Checks whether the class has a named query with the given query name.
1772
     *
1773
     * @param string $queryName
1774
     *
1775
     * @return boolean
1776
     */
1777
    public function hasNamedQuery($queryName) : bool
1778
    {
1779 71
        return isset($this->namedQueries[$queryName]);
1780
    }
1781 71
1782
    /**
1783
     * Checks whether the class has a named native query with the given query name.
1784
     *
1785
     * @param string $queryName
1786
     *
1787
     * @return boolean
1788
     */
1789
    public function hasNamedNativeQuery($queryName) : bool
1790 72
    {
1791
        return isset($this->namedNativeQueries[$queryName]);
1792 72
    }
1793
1794
    /**
1795
     * Checks whether the class has a named native query with the given query name.
1796
     *
1797
     * @param string $name
1798
     *
1799
     * @return boolean
1800
     */
1801
    public function hasSqlResultSetMapping($name) : bool
1802
    {
1803
        return isset($this->sqlResultSetMappings[$name]);
1804
    }
1805
1806
    /**
1807
     * Marks this class as read only, no change tracking is applied to it.
1808
     *
1809
     * @return void
1810 1781
     */
1811
    public function asReadOnly() : void
1812 1781
    {
1813
        $this->readOnly = true;
1814
    }
1815
1816
    /**
1817
     * Whether this class is read only or not.
1818
     *
1819
     * @return bool
1820 1382
     */
1821
    public function isReadOnly() : bool
1822 1382
    {
1823
        return $this->readOnly;
1824
    }
1825
1826
    /**
1827
     * @return bool
1828
     */
1829
    public function isVersioned() : bool
1830 7
    {
1831
        return $this->versionProperty !== null;
1832 7
    }
1833 6
1834 7
    /**
1835
     * Map Embedded Class
1836
     *
1837
     * @param array $mapping
1838 7
     *
1839
     * @throws MappingException
1840
     * @return void
1841
     */
1842
    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...
1843
    {
1844
        /*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...
1845
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1846
        }
1847
1848 2
        $this->embeddedClasses[$mapping['fieldName']] = [
1849
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1850 2
            'columnPrefix'   => $mapping['columnPrefix'],
1851 2
            'declaredField'  => isset($mapping['declaredField']) ? $mapping['declaredField'] : null,
1852
            'originalField'  => isset($mapping['originalField']) ? $mapping['originalField'] : null,
1853 2
            'declaringClass' => $this,
1854
        ];*/
1855
    }
1856
1857
    /**
1858
     * Inline the embeddable class
1859
     *
1860
     * @param string        $property
1861
     * @param ClassMetadata $embeddable
1862
     */
1863
    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...
1864 391
    {
1865
        /*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...
1866 391
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1867
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1868 391
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1869 75
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1870
                ? $property . '.' . $fieldMapping['declaredField']
1871 391
                : $property;
1872
1873
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1874
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1875
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1876
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1877
                    $property,
1878
                    $fieldMapping['columnName'],
1879
                    $this->reflectionClass->getName(),
1880
                    $embeddable->reflectionClass->getName()
1881
                );
1882 139
            }
1883
1884 139
            $this->mapField($fieldMapping);
1885
        }*/
1886
    }
1887
}
1888