Failed Conditions
Push — develop ( d4ed77...d1e561 )
by Marco
16s
created

ClassMetadata::mapEmbedded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 1
dl 0
loc 2
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 string[]
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 471
    public function __construct(
225
        string $entityName,
226
        ClassMetadataBuildingContext $metadataBuildingContext
227
    )
228
    {
229 471
        parent::__construct($entityName, $metadataBuildingContext);
230
231 471
        $this->namingStrategy = $metadataBuildingContext->getNamingStrategy();
232 471
    }
233
234
    /**
235
     * @todo guilhermeblanco Remove once ClassMetadataFactory is finished
236
     *
237
     * @param string $className
238
     */
239 2
    public function setClassName(string $className)
240
    {
241 2
        $this->className = $className;
242 2
    }
243
244
    /**
245
     * @return \ArrayIterator
246
     */
247
    public function getColumnsIterator() : \ArrayIterator
248
    {
249
        $iterator = parent::getColumnsIterator();
250
251
        if ($this->discriminatorColumn) {
252
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), $this->discriminatorColumn);
0 ignored issues
show
Bug introduced by
$this->discriminatorColumn of type Doctrine\ORM\Mapping\DiscriminatorColumnMetadata is incompatible with the type string expected by parameter $newval of ArrayIterator::offsetSet(). ( Ignorable by Annotation )

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

252
            $iterator->offsetSet($this->discriminatorColumn->getColumnName(), /** @scrutinizer ignore-type */ $this->discriminatorColumn);
Loading history...
253
        }
254
255
        return $iterator;
256
    }
257
258
    /**
259
     * @return \ArrayIterator
260
     */
261 11
    public function getAncestorsIterator() : \ArrayIterator
262
    {
263 11
        $ancestors = new \ArrayIterator();
264 11
        $parent    = $this;
265
266 11
        while (($parent = $parent->parent) !== null) {
267 8
            if ($parent instanceof ClassMetadata && $parent->isMappedSuperclass) {
268 1
                continue;
269
            }
270
271 7
            $ancestors->append($parent);
272
        }
273
274 11
        return $ancestors;
275
    }
276
277
    /**
278
     * @return string
279
     */
280 1261
    public function getRootClassName() : string
281
    {
282 1261
        return ($this->parent instanceof ClassMetadata && ! $this->parent->isMappedSuperclass)
283 400
            ? $this->parent->getRootClassName()
284 1261
            : $this->className
285
        ;
286
    }
287
288
    /**
289
     * Handles metadata cloning nicely.
290
     */
291 13
    public function __clone()
292
    {
293 13
        if ($this->cache) {
294 12
            $this->cache = clone $this->cache;
295
        }
296
297 13
        foreach ($this->declaredProperties as $name => $property) {
298 13
            $this->declaredProperties[$name] = clone $property;
299
        }
300 13
    }
301
302
    /**
303
     * Creates a string representation of this instance.
304
     *
305
     * @return string The string representation of this instance.
306
     *
307
     * @todo Construct meaningful string representation.
308
     */
309
    public function __toString()
310
    {
311
        return __CLASS__ . '@' . spl_object_id($this);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

311
        return __CLASS__ . '@' . /** @scrutinizer ignore-call */ spl_object_id($this);
Loading history...
312
    }
313
314
    /**
315
     * Determines which fields get serialized.
316
     *
317
     * It is only serialized what is necessary for best unserialization performance.
318
     * That means any metadata properties that are not set or empty or simply have
319
     * their default value are NOT serialized.
320
     *
321
     * Parts that are also NOT serialized because they can not be properly unserialized:
322
     * - reflectionClass
323
     *
324
     * @return array The names of all the fields that should be serialized.
325
     */
326 5
    public function __sleep()
327
    {
328 5
        $serialized = [];
329
330
        // This metadata is always serialized/cached.
331 5
        $serialized = array_merge($serialized, [
332 5
            'declaredProperties',
333
            'fieldNames',
334
            //'embeddedClasses',
335
            'identifier',
336
            'className',
337
            'parent',
338
            'table',
339
            'valueGenerationPlan',
340
        ]);
341
342
        // The rest of the metadata is only serialized if necessary.
343 5
        if ($this->changeTrackingPolicy !== ChangeTrackingPolicy::DEFERRED_IMPLICIT) {
344
            $serialized[] = 'changeTrackingPolicy';
345
        }
346
347 5
        if ($this->customRepositoryClassName) {
348 1
            $serialized[] = 'customRepositoryClassName';
349
        }
350
351 5
        if ($this->inheritanceType !== InheritanceType::NONE) {
352 1
            $serialized[] = 'inheritanceType';
353 1
            $serialized[] = 'discriminatorColumn';
354 1
            $serialized[] = 'discriminatorValue';
355 1
            $serialized[] = 'discriminatorMap';
356 1
            $serialized[] = 'subClasses';
357
        }
358
359 5
        if ($this->isMappedSuperclass) {
360
            $serialized[] = 'isMappedSuperclass';
361
        }
362
363 5
        if ($this->isEmbeddedClass) {
364
            $serialized[] = 'isEmbeddedClass';
365
        }
366
367 5
        if ($this->isVersioned()) {
368
            $serialized[] = 'versionProperty';
369
        }
370
371 5
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array<string,string[]> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
372
            $serialized[] = 'lifecycleCallbacks';
373
        }
374
375 5
        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...
376 1
            $serialized[] = 'entityListeners';
377
        }
378
379 5
        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...
380 1
            $serialized[] = 'namedQueries';
381
        }
382
383 5
        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...
384
            $serialized[] = 'namedNativeQueries';
385
        }
386
387 5
        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...
388
            $serialized[] = 'sqlResultSetMappings';
389
        }
390
391 5
        if ($this->cache) {
392
            $serialized[] = 'cache';
393
        }
394
395 5
        if ($this->readOnly) {
396 1
            $serialized[] = 'readOnly';
397
        }
398
399 5
        return $serialized;
400
    }
401
402
    /**
403
     * Restores some state that can not be serialized/unserialized.
404
     *
405
     * @param ReflectionService $reflectionService
406
     *
407
     * @return void
408
     */
409 1637
    public function wakeupReflection(ReflectionService $reflectionService) : void
410
    {
411
        // Restore ReflectionClass and properties
412 1637
        $this->reflectionClass = $reflectionService->getClass($this->className);
413
414 1637
        if (! $this->reflectionClass) {
415
            return;
416
        }
417
418 1637
        $this->className = $this->reflectionClass->getName();
419
420 1637
        foreach ($this->declaredProperties as $property) {
421
            /** @var Property $property */
422 1636
            $property->wakeupReflection($reflectionService);
423
        }
424 1637
    }
425
426
    /**
427
     * Validates Identifier.
428
     *
429
     * @return void
430
     *
431
     * @throws MappingException
432
     */
433 364
    public function validateIdentifier() : void
434
    {
435 364
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
436 30
            return;
437
        }
438
439
        // Verify & complete identifier mapping
440 363
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
441 5
            throw MappingException::identifierRequired($this->className);
442
        }
443
444 358
        $explicitlyGeneratedProperties = array_filter($this->declaredProperties, function (Property $property) : bool {
445 358
            return $property instanceof FieldMetadata
446 358
                && $property->isPrimaryKey()
447 358
                && $property->hasValueGenerator();
448 358
        });
449
450 358
        if ($explicitlyGeneratedProperties && $this->isIdentifierComposite()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $explicitlyGeneratedProperties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
451
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->className);
452
        }
453 358
    }
454
455
    /**
456
     * Validates association targets actually exist.
457
     *
458
     * @return void
459
     *
460
     * @throws MappingException
461
     */
462 362
    public function validateAssociations() : void
463
    {
464 362
        array_map(
465 362
            function (Property $property) {
466 362
                if (! ($property instanceof AssociationMetadata)) {
467 358
                    return;
468
                }
469
470 248
                $targetEntity = $property->getTargetEntity();
471
472 248
                if (! class_exists($targetEntity)) {
473 1
                    throw MappingException::invalidTargetEntityClass($targetEntity, $this->className, $property->getName());
474
                }
475 362
            },
476 362
            $this->declaredProperties
477
        );
478 361
    }
479
480
    /**
481
     * Validates lifecycle callbacks.
482
     *
483
     * @param ReflectionService $reflectionService
484
     *
485
     * @return void
486
     *
487
     * @throws MappingException
488
     */
489 362
    public function validateLifecycleCallbacks(ReflectionService $reflectionService) : void
490
    {
491 362
        foreach ($this->lifecycleCallbacks as $callbacks) {
492
            /** @var array $callbacks */
493 11
            foreach ($callbacks as $callbackFuncName) {
494 11
                if (! $reflectionService->hasPublicMethod($this->className, $callbackFuncName)) {
495 11
                    throw MappingException::lifecycleCallbackMethodNotFound($this->className, $callbackFuncName);
496
                }
497
            }
498
        }
499 361
    }
500
501
    /**
502
     * Sets the change tracking policy used by this class.
503
     *
504
     * @param string $policy
505
     *
506
     * @return void
507
     */
508 104
    public function setChangeTrackingPolicy(string $policy) : void
509
    {
510 104
        $this->changeTrackingPolicy = $policy;
511 104
    }
512
513
    /**
514
     * Checks whether a field is part of the identifier/primary key field(s).
515
     *
516
     * @param string $fieldName The field name.
517
     *
518
     * @return bool TRUE if the field is part of the table identifier/primary key field(s), FALSE otherwise.
519
     */
520 1024
    public function isIdentifier(string $fieldName) : bool
521
    {
522 1024
        if (! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
523 1
            return false;
524
        }
525
526 1023
        if (! $this->isIdentifierComposite()) {
527 1019
            return $fieldName === $this->identifier[0];
528
        }
529
530 90
        return in_array($fieldName, $this->identifier, true);
531
    }
532
533
    /**
534
     * @return bool
535
     */
536 1208
    public function isIdentifierComposite() : bool
537
    {
538 1208
        return isset($this->identifier[1]);
539
    }
540
541
    /**
542
     * Gets the named query.
543
     *
544
     * @see ClassMetadata::$namedQueries
545
     *
546
     * @param string $queryName The query name.
547
     *
548
     * @return string
549
     *
550
     * @throws MappingException
551
     */
552 4
    public function getNamedQuery($queryName) : string
553
    {
554 4
        if (! isset($this->namedQueries[$queryName])) {
555 1
            throw MappingException::queryNotFound($this->className, $queryName);
556
        }
557
558 3
        return $this->namedQueries[$queryName];
559
    }
560
561
    /**
562
     * Gets all named queries of the class.
563
     *
564
     * @return array
565
     */
566 103
    public function getNamedQueries() : array
567
    {
568 103
        return $this->namedQueries;
569
    }
570
571
    /**
572
     * Gets the named native query.
573
     *
574
     * @see ClassMetadata::$namedNativeQueries
575
     *
576
     * @param string $queryName The query name.
577
     *
578
     * @return array
579
     *
580
     * @throws MappingException
581
     */
582 15
    public function getNamedNativeQuery($queryName) : array
583
    {
584 15
        if ( ! isset($this->namedNativeQueries[$queryName])) {
585
            throw MappingException::queryNotFound($this->className, $queryName);
586
        }
587
588 15
        return $this->namedNativeQueries[$queryName];
589
    }
590
591
    /**
592
     * Gets all named native queries of the class.
593
     *
594
     * @return array
595
     */
596 3
    public function getNamedNativeQueries() : array
597
    {
598 3
        return $this->namedNativeQueries;
599
    }
600
601
    /**
602
     * Gets the result set mapping.
603
     *
604
     * @see ClassMetadata::$sqlResultSetMappings
605
     *
606
     * @param string $name The result set mapping name.
607
     *
608
     * @return array
609
     *
610
     * @throws MappingException
611
     */
612 13
    public function getSqlResultSetMapping($name)
613
    {
614 13
        if (! isset($this->sqlResultSetMappings[$name])) {
615
            throw MappingException::resultMappingNotFound($this->className, $name);
616
        }
617
618 13
        return $this->sqlResultSetMappings[$name];
619
    }
620
621
    /**
622
     * Gets all sql result set mappings of the class.
623
     *
624
     * @return array
625
     */
626 5
    public function getSqlResultSetMappings()
627
    {
628 5
        return $this->sqlResultSetMappings;
629
    }
630
631
    /**
632
     * Validates & completes the basic mapping information for field mapping.
633
     *
634
     * @param FieldMetadata $property
635
     *
636
     * @throws MappingException If something is wrong with the mapping.
637
     */
638 406
    protected function validateAndCompleteFieldMapping(FieldMetadata $property)
639
    {
640 406
        $fieldName  = $property->getName();
641 406
        $columnName = $property->getColumnName();
642
643 406
        if (empty($columnName)) {
644 349
            $columnName = $this->namingStrategy->propertyToColumnName($fieldName, $this->className);
645
646 349
            $property->setColumnName($columnName);
647
        }
648
649 406
        if (! $this->isMappedSuperclass) {
650 399
            $property->setTableName($this->getTableName());
651
        }
652
653
        // Check for already declared column
654 406
        if (isset($this->fieldNames[$columnName]) ||
655 406
            ($this->discriminatorColumn !== null && $this->discriminatorColumn->getColumnName() === $columnName)) {
656 2
            throw MappingException::duplicateColumnName($this->className, $columnName);
657
        }
658
659
        // Complete id mapping
660 405
        if ($property->isPrimaryKey()) {
661 389
            if ($this->versionProperty !== null && $this->versionProperty->getName() === $fieldName) {
662
                throw MappingException::cannotVersionIdField($this->className, $fieldName);
663
            }
664
665 389
            if ($property->getType()->canRequireSQLConversion()) {
666
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($property);
0 ignored issues
show
Bug introduced by
$property of type Doctrine\ORM\Mapping\FieldMetadata is incompatible with the type string expected by parameter $className of Doctrine\ORM\Mapping\Map...rPrimaryKeyProperties(). ( Ignorable by Annotation )

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

666
                throw MappingException::sqlConversionNotAllowedForPrimaryKeyProperties(/** @scrutinizer ignore-type */ $property);
Loading history...
Bug introduced by
The call to Doctrine\ORM\Mapping\Map...rPrimaryKeyProperties() has too few arguments starting with property. ( Ignorable by Annotation )

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

666
                throw MappingException::/** @scrutinizer ignore-call */ sqlConversionNotAllowedForPrimaryKeyProperties($property);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
667
            }
668
669 389
            if (! in_array($fieldName, $this->identifier)) {
670 389
                $this->identifier[] = $fieldName;
671
            }
672
        }
673
674 405
        $this->fieldNames[$columnName] = $fieldName;
675 405
    }
676
677
    /**
678
     * Validates & completes the basic mapping information for field mapping.
679
     *
680
     * @param VersionFieldMetadata $property
681
     *
682
     * @throws MappingException If something is wrong with the mapping.
683
     */
684 21
    protected function validateAndCompleteVersionFieldMapping(VersionFieldMetadata $property)
685
    {
686 21
        $this->versionProperty = $property;
687
688 21
        $options = $property->getOptions();
689
690 21
        if (isset($options['default'])) {
691
            return;
692
        }
693
694 21
        if (in_array($property->getTypeName(), ['integer', 'bigint', 'smallint'])) {
695 20
            $property->setOptions(array_merge($options, ['default' => 1]));
696
697 20
            return;
698
        }
699
700 2
        if ($property->getTypeName() === 'datetime') {
701 1
            $property->setOptions(array_merge($options, ['default' => 'CURRENT_TIMESTAMP']));
702
703 1
            return;
704
        }
705
706 1
        throw MappingException::unsupportedOptimisticLockingType($property->getType());
707
    }
708
709
    /**
710
     * Validates & completes the basic mapping information that is common to all
711
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
712
     *
713
     * @param AssociationMetadata $property
714
     *
715
     * @throws MappingException If something is wrong with the mapping.
716
     * @throws CacheException   If entity is not cacheable.
717
     */
718 283
    protected function validateAndCompleteAssociationMapping(AssociationMetadata $property)
719
    {
720 283
        $fieldName    = $property->getName();
721 283
        $targetEntity = $property->getTargetEntity();
722
723 283
        if (! $targetEntity) {
724
            throw MappingException::missingTargetEntity($fieldName);
725
        }
726
727 283
        $property->setSourceEntity($this->className);
728 283
        $property->setOwningSide($property->getMappedBy() === null);
729 283
        $property->setTargetEntity($targetEntity);
730
731
        // Complete id mapping
732 283
        if ($property->isPrimaryKey()) {
733 44
            if ($property->isOrphanRemoval()) {
734 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->className, $fieldName);
735
            }
736
737 43
            if ( ! in_array($property->getName(), $this->identifier)) {
738 43
                if ($property instanceof ToOneAssociationMetadata && count($property->getJoinColumns()) >= 2) {
739
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
740
                        $property->getTargetEntity(),
741
                        $this->className,
742
                        $fieldName
743
                    );
744
                }
745
746 43
                $this->identifier[] = $property->getName();
747
            }
748
749 43
            if ($this->cache && !$property->getCache()) {
750 2
                throw CacheException::nonCacheableEntityAssociation($this->className, $fieldName);
751
            }
752
753 41
            if ($property instanceof ToManyAssociationMetadata) {
754 1
                throw MappingException::illegalToManyIdentifierAssociation($this->className, $property->getName());
755
            }
756
        }
757
758
        // Cascades
759 279
        $cascadeTypes = ['remove', 'persist', 'refresh'];
760 279
        $cascades     = array_map('strtolower', $property->getCascade());
761
762 279
        if (in_array('all', $cascades)) {
763 4
            $cascades = $cascadeTypes;
764
        }
765
766 279
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
767 1
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
768
769 1
            throw MappingException::invalidCascadeOption($diffCascades, $this->className, $fieldName);
770
        }
771
772 278
        $property->setCascade($cascades);
773 278
    }
774
775
    /**
776
     * Validates & completes a to-one association mapping.
777
     *
778
     * @param ToOneAssociationMetadata $property The association mapping to validate & complete.
779
     *
780
     * @throws \RuntimeException
781
     * @throws MappingException
782
     */
783 243
    protected function validateAndCompleteToOneAssociationMetadata(ToOneAssociationMetadata $property)
784
    {
785 243
        $fieldName = $property->getName();
786
787 243
        if ($property->getJoinColumns()) {
788 171
            $property->setOwningSide(true);
789
        }
790
791 243
        if ($property->isOwningSide()) {
792 240
            if (empty($property->getJoinColumns())) {
793
                // Apply default join column
794 82
                $property->addJoinColumn(new JoinColumnMetadata());
795
            }
796
797 240
            $uniqueConstraintColumns = [];
798
799 240
            foreach ($property->getJoinColumns() as $joinColumn) {
800
                /** @var JoinColumnMetadata $joinColumn */
801 240
                if ($property instanceof OneToOneAssociationMetadata && $this->inheritanceType !== InheritanceType::SINGLE_TABLE) {
802 118
                    if (1 === count($property->getJoinColumns())) {
803 116
                        if (! $property->isPrimaryKey()) {
804 116
                            $joinColumn->setUnique(true);
805
                        }
806
                    } else {
807 2
                        $uniqueConstraintColumns[] = $joinColumn->getColumnName();
808
                    }
809
                }
810
811 240
                $joinColumn->setTableName(! $this->isMappedSuperclass ? $this->getTableName() : null);
812
813 240
                if (! $joinColumn->getColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getColumnName() 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...
814 101
                    $joinColumn->setColumnName($this->namingStrategy->joinColumnName($fieldName, $this->className));
815
                }
816
817 240
                if (! $joinColumn->getReferencedColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getReferencedColumnName() 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...
818 82
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
819
                }
820
821 240
                $this->fieldNames[$joinColumn->getColumnName()] = $fieldName;
822
            }
823
824 240
            if ($uniqueConstraintColumns) {
825 2
                if ( ! $this->table) {
826
                    throw new \RuntimeException(
827
                        "ClassMetadata::setTable() has to be called before defining a one to one relationship."
828
                    );
829
                }
830
831 2
                $this->table->addUniqueConstraint(
832
                    [
833 2
                        'name'    => sprintf('%s_uniq', $fieldName),
834 2
                        'columns' => $uniqueConstraintColumns,
835
                        'options' => [],
836
                        'flags'   => [],
837
                    ]
838
                );
839
            }
840
        }
841
842 243
        if ($property->isOrphanRemoval()) {
843 9
            $cascades = $property->getCascade();
844
845 9
            if (! in_array('remove', $cascades)) {
846 8
                $cascades[] = 'remove';
847
848 8
                $property->setCascade($cascades);
849
            }
850
851
            // @todo guilhermeblanco where is this used?
852
            // @todo guilhermeblanco Shouldn￿'t we iterate through JoinColumns to set non-uniqueness?
853
            //$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...
854
        }
855
856 243
        if ($property->isPrimaryKey() && ! $property->isOwningSide()) {
857 1
            throw MappingException::illegalInverseIdentifierAssociation($this->className, $fieldName);
858
        }
859 242
    }
860
861
    /**
862
     * Validates & completes a to-many association mapping.
863
     *
864
     * @param ToManyAssociationMetadata $property The association mapping to validate & complete.
865
     *
866
     * @throws MappingException
867
     */
868 167
    protected function validateAndCompleteToManyAssociationMetadata(ToManyAssociationMetadata $property)
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
869
    {
870
        // Do nothing
871 167
    }
872
873
    /**
874
     * Validates & completes a one-to-one association mapping.
875
     *
876
     * @param OneToOneAssociationMetadata $property The association mapping to validate & complete.
877
     */
878 125
    protected function validateAndCompleteOneToOneMapping(OneToOneAssociationMetadata $property)
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
879
    {
880
        // Do nothing
881 125
    }
882
883
    /**
884
     * Validates & completes a many-to-one association mapping.
885
     *
886
     * @param ManyToOneAssociationMetadata $property The association mapping to validate & complete.
887
     *
888
     * @throws MappingException
889
     */
890 141
    protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
891
    {
892
        // A many-to-one mapping is essentially a one-one backreference
893 141
        if ($property->isOrphanRemoval()) {
894
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
895
        }
896 141
    }
897
898
    /**
899
     * Validates & completes a one-to-many association mapping.
900
     *
901
     * @param OneToManyAssociationMetadata $property The association mapping to validate & complete.
902
     *
903
     * @throws MappingException
904
     */
905 114
    protected function validateAndCompleteOneToManyMapping(OneToManyAssociationMetadata $property)
906
    {
907
        // OneToMany MUST be inverse side
908 114
        $property->setOwningSide(false);
909
910
        // OneToMany MUST have mappedBy
911 114
        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...
912
            throw MappingException::oneToManyRequiresMappedBy($property->getName());
913
        }
914
915 114
        if ($property->isOrphanRemoval()) {
916 23
            $cascades = $property->getCascade();
917
918 23
            if (! in_array('remove', $cascades)) {
919 20
                $cascades[] = 'remove';
920
921 20
                $property->setCascade($cascades);
922
            }
923
        }
924 114
    }
925
926
    /**
927
     * Validates & completes a many-to-many association mapping.
928
     *
929
     * @param ManyToManyAssociationMetadata $property The association mapping to validate & complete.
930
     *
931
     * @throws MappingException
932
     */
933 105
    protected function validateAndCompleteManyToManyMapping(ManyToManyAssociationMetadata $property)
934
    {
935 105
        if ($property->isOwningSide()) {
936
            // owning side MUST have a join table
937 93
            $joinTable = $property->getJoinTable() ?: new JoinTableMetadata();
938
939 93
            $property->setJoinTable($joinTable);
940
941 93
            if (! $joinTable->getName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinTable->getName() 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...
942 18
                $joinTableName = $this->namingStrategy->joinTableName(
943 18
                    $property->getSourceEntity(),
944 18
                    $property->getTargetEntity(),
945 18
                    $property->getName()
946
                );
947
948 18
                $joinTable->setName($joinTableName);
949
            }
950
951 93
            $selfReferencingEntityWithoutJoinColumns = $property->getSourceEntity() == $property->getTargetEntity() && ! $joinTable->hasColumns();
952
953 93
            if (! $joinTable->getJoinColumns()) {
954 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
955 16
                $sourceReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'source' : $referencedColumnName;
956 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getSourceEntity(), $sourceReferenceName);
957 16
                $joinColumn           = new JoinColumnMetadata();
958
959 16
                $joinColumn->setColumnName($columnName);
960 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
961 16
                $joinColumn->setOnDelete('CASCADE');
962
963 16
                $joinTable->addJoinColumn($joinColumn);
964
            }
965
966 93
            if (! $joinTable->getInverseJoinColumns()) {
967 16
                $referencedColumnName = $this->namingStrategy->referenceColumnName();
968 16
                $targetReferenceName  = $selfReferencingEntityWithoutJoinColumns ? 'target' : $referencedColumnName;
969 16
                $columnName           = $this->namingStrategy->joinKeyColumnName($property->getTargetEntity(), $targetReferenceName);
970 16
                $joinColumn           = new JoinColumnMetadata();
971
972 16
                $joinColumn->setColumnName($columnName);
973 16
                $joinColumn->setReferencedColumnName($referencedColumnName);
974 16
                $joinColumn->setOnDelete('CASCADE');
975
976 16
                $joinTable->addInverseJoinColumn($joinColumn);
977
            }
978
979 93
            foreach ($joinTable->getJoinColumns() as $joinColumn) {
980
                /** @var JoinColumnMetadata $joinColumn */
981 93
                if (! $joinColumn->getReferencedColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getReferencedColumnName() 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...
982 2
                    $joinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
983
                }
984
985 93
                $referencedColumnName = $joinColumn->getReferencedColumnName();
986
987 93
                if (! $joinColumn->getColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $joinColumn->getColumnName() 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...
988 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
989 2
                        $property->getSourceEntity(),
990 2
                        $referencedColumnName
991
                    );
992
993 93
                    $joinColumn->setColumnName($columnName);
994
                }
995
            }
996
997 93
            foreach ($joinTable->getInverseJoinColumns() as $inverseJoinColumn) {
998
                /** @var JoinColumnMetadata $inverseJoinColumn */
999 93
                if (! $inverseJoinColumn->getReferencedColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inverseJoinColumn->getReferencedColumnName() 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...
1000 2
                    $inverseJoinColumn->setReferencedColumnName($this->namingStrategy->referenceColumnName());
1001
                }
1002
1003 93
                $referencedColumnName = $inverseJoinColumn->getReferencedColumnName();
1004
1005 93
                if (! $inverseJoinColumn->getColumnName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inverseJoinColumn->getColumnName() 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...
1006 2
                    $columnName = $this->namingStrategy->joinKeyColumnName(
1007 2
                        $property->getTargetEntity(),
1008 2
                        $referencedColumnName
1009
                    );
1010
1011 93
                    $inverseJoinColumn->setColumnName($columnName);
1012
                }
1013
            }
1014
        }
1015 105
    }
1016
1017
    /**
1018
     * {@inheritDoc}
1019
     */
1020 397
    public function getIdentifierFieldNames()
1021
    {
1022 397
        return $this->identifier;
1023
    }
1024
1025
    /**
1026
     * Gets the name of the single id field. Note that this only works on
1027
     * entity classes that have a single-field pk.
1028
     *
1029
     * @return string
1030
     *
1031
     * @throws MappingException If the class has a composite primary key.
1032
     */
1033 149
    public function getSingleIdentifierFieldName()
1034
    {
1035 149
        if ($this->isIdentifierComposite()) {
1036 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->className);
1037
        }
1038
1039 148
        if ( ! isset($this->identifier[0])) {
1040 1
            throw MappingException::noIdDefined($this->className);
1041
        }
1042
1043 147
        return $this->identifier[0];
1044
    }
1045
1046
    /**
1047
     * INTERNAL:
1048
     * Sets the mapped identifier/primary key fields of this class.
1049
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1050
     *
1051
     * @param array $identifier
1052
     *
1053
     * @return void
1054
     */
1055 101
    public function setIdentifier(array $identifier)
1056
    {
1057 101
        $this->identifier = $identifier;
1058 101
    }
1059
1060
    /**
1061
     * {@inheritDoc}
1062
     */
1063 1049
    public function getIdentifier()
1064
    {
1065 1049
        return $this->identifier;
1066
    }
1067
1068
    /**
1069
     * {@inheritDoc}
1070
     */
1071 186
    public function hasField($fieldName)
1072
    {
1073 186
        return isset($this->declaredProperties[$fieldName])
1074 186
            && $this->declaredProperties[$fieldName] instanceof FieldMetadata;
1075
    }
1076
1077
    /**
1078
     * Returns an array with identifier column names and their corresponding ColumnMetadata.
1079
     *
1080
     * @param EntityManagerInterface $em
1081
     *
1082
     * @return array
1083
     */
1084 434
    public function getIdentifierColumns(EntityManagerInterface $em) : array
1085
    {
1086 434
        $columns = [];
1087
1088 434
        foreach ($this->identifier as $idProperty) {
1089 434
            $property = $this->getProperty($idProperty);
1090
1091 434
            if ($property instanceof FieldMetadata) {
1092 429
                $columns[$property->getColumnName()] = $property;
1093
1094 429
                continue;
1095
            }
1096
1097
            /** @var AssociationMetadata $property */
1098
1099
            // Association defined as Id field
1100 24
            $targetClass = $em->getClassMetadata($property->getTargetEntity());
1101
1102 24
            if (! $property->isOwningSide()) {
1103
                $property    = $targetClass->getProperty($property->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

1103
                /** @scrutinizer ignore-call */ 
1104
                $property    = $targetClass->getProperty($property->getMappedBy());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1104
                $targetClass = $em->getClassMetadata($property->getTargetEntity());
1105
            }
1106
1107 24
            $joinColumns = $property instanceof ManyToManyAssociationMetadata
1108
                ? $property->getJoinTable()->getInverseJoinColumns()
1109 24
                : $property->getJoinColumns()
0 ignored issues
show
Bug introduced by
The method getJoinColumns() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

1109
                : $property->/** @scrutinizer ignore-call */ getJoinColumns()
Loading history...
1110
            ;
1111
1112 24
            foreach ($joinColumns as $joinColumn) {
1113
                /** @var JoinColumnMetadata $joinColumn */
1114 24
                $columnName           = $joinColumn->getColumnName();
1115 24
                $referencedColumnName = $joinColumn->getReferencedColumnName();
1116
1117 24
                if (! $joinColumn->getType()) {
1118 12
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $em));
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

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

1118
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, /** @scrutinizer ignore-type */ $targetClass, $em));
Loading history...
1119
                }
1120
1121 24
                $columns[$columnName] = $joinColumn;
1122
            }
1123
        }
1124
1125 434
        return $columns;
1126
    }
1127
1128
    /**
1129
     * Gets the name of the primary table.
1130
     *
1131
     * @return string|null
1132
     */
1133 1585
    public function getTableName() : ?string
1134
    {
1135 1585
        return $this->table->getName();
1136
    }
1137
1138
    /**
1139
     * Gets primary table's schema name.
1140
     *
1141
     * @return string|null
1142
     */
1143 14
    public function getSchemaName() : ?string
1144
    {
1145 14
        return $this->table->getSchema();
1146
    }
1147
1148
    /**
1149
     * Gets the table name to use for temporary identifier tables of this class.
1150
     *
1151
     * @return string
1152
     */
1153 7
    public function getTemporaryIdTableName() : string
1154
    {
1155 7
        $schema = null === $this->getSchemaName()
1156 6
            ? ''
1157 7
            : $this->getSchemaName() . '_'
1158
        ;
1159
1160
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
1161 7
        return $schema . $this->getTableName() . '_id_tmp';
1162
    }
1163
1164
    /**
1165
     * Sets the mapped subclasses of this class.
1166
     *
1167
     * @todo guilhermeblanco Only used for ClassMetadataTest. Remove if possible!
1168
     *
1169
     * @param array $subclasses The names of all mapped subclasses.
1170
     *
1171
     * @return void
1172
     */
1173 4
    public function setSubclasses(array $subclasses) : void
1174
    {
1175 4
        foreach ($subclasses as $subclass) {
1176 3
            $this->subClasses[] = $subclass;
1177
        }
1178 4
    }
1179
1180
    /**
1181
     * @return array
1182
     */
1183 1075
    public function getSubClasses() : array
1184
    {
1185 1075
        return $this->subClasses;
1186
    }
1187
1188
    /**
1189
     * Sets the inheritance type used by the class and its subclasses.
1190
     *
1191
     * @param integer $type
1192
     *
1193
     * @return void
1194
     *
1195
     * @throws MappingException
1196
     */
1197 119
    public function setInheritanceType($type) : void
1198
    {
1199 119
        if ( ! $this->isInheritanceType($type)) {
1200
            throw MappingException::invalidInheritanceType($this->className, $type);
1201
        }
1202
1203 119
        $this->inheritanceType = $type;
1204 119
    }
1205
1206
    /**
1207
     * Sets the override property mapping for an entity relationship.
1208
     *
1209
     * @param Property $property
1210
     *
1211
     * @return void
1212
     *
1213
     * @throws \RuntimeException
1214
     * @throws MappingException
1215
     * @throws CacheException
1216
     */
1217 12
    public function setPropertyOverride(Property $property) : void
1218
    {
1219 12
        $fieldName = $property->getName();
1220
1221 12
        if (! isset($this->declaredProperties[$fieldName])) {
1222 2
            throw MappingException::invalidOverrideFieldName($this->className, $fieldName);
1223
        }
1224
1225 10
        $originalProperty          = $this->getProperty($fieldName);
1226 10
        $originalPropertyClassName = get_class($originalProperty);
1227
1228
        // If moving from transient to persistent, assume it's a new property
1229 10
        if ($originalPropertyClassName === TransientMetadata::class) {
1230 1
            unset($this->declaredProperties[$fieldName]);
1231
1232 1
            $this->addProperty($property);
1233
1234 1
            return;
1235
        }
1236
1237
        // Do not allow to change property type
1238 9
        if ($originalPropertyClassName !== get_class($property)) {
1239
            throw MappingException::invalidOverridePropertyType($this->className, $fieldName);
1240
        }
1241
1242
        // Do not allow to change version property
1243 9
        if ($originalProperty instanceof VersionFieldMetadata) {
1244
            throw MappingException::invalidOverrideVersionField($this->className, $fieldName);
1245
        }
1246
1247 9
        unset($this->declaredProperties[$fieldName]);
1248
1249 9
        if ($property instanceof FieldMetadata) {
1250
            // Unset defined fieldName prior to override
1251 5
            unset($this->fieldNames[$originalProperty->getColumnName()]);
0 ignored issues
show
Bug introduced by
The method getColumnName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

1251
            unset($this->fieldNames[$originalProperty->/** @scrutinizer ignore-call */ getColumnName()]);
Loading history...
1252
1253
            // Revert what should not be allowed to change
1254 5
            $property->setDeclaringClass($originalProperty->getDeclaringClass());
1255 5
            $property->setPrimaryKey($originalProperty->isPrimaryKey());
1256 9
        } elseif ($property instanceof AssociationMetadata) {
1257
            // Unset all defined fieldNames prior to override
1258 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $originalProperty->isOwningSide()) {
1259 5
                foreach ($originalProperty->getJoinColumns() as $joinColumn) {
1260 5
                    unset($this->fieldNames[$joinColumn->getColumnName()]);
1261
                }
1262
            }
1263
1264
            // Override what it should be allowed to change
1265 9
            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...
1266 2
                $originalProperty->setInversedBy($property->getInversedBy());
0 ignored issues
show
Bug introduced by
The method setInversedBy() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

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

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

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

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

1270
                $originalProperty->/** @scrutinizer ignore-call */ 
1271
                                   setFetchMode($property->getFetchMode());
Loading history...
1271
            }
1272
1273 9
            if ($originalProperty instanceof ToOneAssociationMetadata && $property->getJoinColumns()) {
1274 5
                $originalProperty->setJoinColumns($property->getJoinColumns());
1275 8
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->getJoinTable()) {
0 ignored issues
show
Bug introduced by
The method getJoinTable() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

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

1275
            } elseif ($originalProperty instanceof ManyToManyAssociationMetadata && $property->/** @scrutinizer ignore-call */ getJoinTable()) {
Loading history...
1276 4
                $originalProperty->setJoinTable($property->getJoinTable());
1277
            }
1278
1279 9
            $property = $originalProperty;
1280
        }
1281
1282 9
        $this->addProperty($property);
0 ignored issues
show
Bug introduced by
It seems like $property can also be of type null; however, parameter $property of Doctrine\ORM\Mapping\ClassMetadata::addProperty() does only seem to accept Doctrine\ORM\Mapping\Property, maybe add an additional type check? ( Ignorable by Annotation )

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

1282
        $this->addProperty(/** @scrutinizer ignore-type */ $property);
Loading history...
1283 9
    }
1284
1285
    /**
1286
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
1287
     *
1288
     * @return bool
1289
     */
1290 334
    public function isRootEntity()
1291
    {
1292 334
        return $this->className === $this->getRootClassName();
1293
    }
1294
1295
    /**
1296
     * Checks whether a mapped field is inherited from a superclass.
1297
     *
1298
     * @param string $fieldName
1299
     *
1300
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
1301
     */
1302 616
    public function isInheritedProperty($fieldName)
1303
    {
1304 616
        $declaringClass = $this->declaredProperties[$fieldName]->getDeclaringClass();
1305
1306 616
        return ! ($declaringClass->className === $this->className);
1307
    }
1308
1309
    /**
1310
     * {@inheritdoc}
1311
     */
1312 451
    public function setTable(TableMetadata $table) : void
1313
    {
1314 451
        $this->table = $table;
1315
1316 451
        if (empty($table->getName())) {
1317
            $table->setName($this->namingStrategy->classToTableName($this->className));
1318
        }
1319 451
    }
1320
1321
    /**
1322
     * Checks whether the given type identifies an inheritance type.
1323
     *
1324
     * @param integer $type
1325
     *
1326
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
1327
     */
1328 119
    private function isInheritanceType($type)
1329
    {
1330 119
        return $type == InheritanceType::NONE
1331 92
            || $type == InheritanceType::SINGLE_TABLE
1332 52
            || $type == InheritanceType::JOINED
1333 119
            || $type == InheritanceType::TABLE_PER_CLASS;
1334
    }
1335
1336
    /**
1337
     * @param string $columnName
1338
     *
1339
     * @return LocalColumnMetadata|null
1340
     */
1341 911
    public function getColumn(string $columnName): ?LocalColumnMetadata
1342
    {
1343 911
        foreach ($this->declaredProperties as $property) {
1344 911
            if ($property instanceof LocalColumnMetadata && $property->getColumnName() === $columnName) {
1345 911
                return $property;
1346
            }
1347
        }
1348
1349
        return null;
1350
    }
1351
1352
    /**
1353
     * Add a property mapping.
1354
     *
1355
     * @param Property $property
1356
     *
1357
     * @throws \RuntimeException
1358
     * @throws MappingException
1359
     * @throws CacheException
1360
     */
1361 429
    public function addProperty(Property $property)
1362
    {
1363 429
        $fieldName = $property->getName();
1364
1365
        // Check for empty field name
1366 429
        if (empty($fieldName)) {
1367 1
            throw MappingException::missingFieldName($this->className);
1368
        }
1369
1370 428
        $property->setDeclaringClass($this);
1371
1372
        switch (true) {
1373 428
            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...
1374 21
                $this->validateAndCompleteFieldMapping($property);
1375 21
                $this->validateAndCompleteVersionFieldMapping($property);
1376 20
                break;
1377
1378 427
            case ($property instanceof FieldMetadata):
1379 405
                $this->validateAndCompleteFieldMapping($property);
1380 404
                break;
1381
1382 286
            case ($property instanceof OneToOneAssociationMetadata):
1383 127
                $this->validateAndCompleteAssociationMapping($property);
1384 126
                $this->validateAndCompleteToOneAssociationMetadata($property);
1385 125
                $this->validateAndCompleteOneToOneMapping($property);
1386 125
                break;
1387
1388 223
            case ($property instanceof OneToManyAssociationMetadata):
1389 114
                $this->validateAndCompleteAssociationMapping($property);
1390 114
                $this->validateAndCompleteToManyAssociationMetadata($property);
1391 114
                $this->validateAndCompleteOneToManyMapping($property);
1392 114
                break;
1393
1394 219
            case ($property instanceof ManyToOneAssociationMetadata):
1395 144
                $this->validateAndCompleteAssociationMapping($property);
1396 141
                $this->validateAndCompleteToOneAssociationMetadata($property);
1397 141
                $this->validateAndCompleteManyToOneMapping($property);
1398 141
                break;
1399
1400 123
            case ($property instanceof ManyToManyAssociationMetadata):
1401 106
                $this->validateAndCompleteAssociationMapping($property);
1402 105
                $this->validateAndCompleteToManyAssociationMetadata($property);
1403 105
                $this->validateAndCompleteManyToManyMapping($property);
1404 105
                break;
1405
1406
            default:
1407
                // Transient properties are ignored on purpose here! =)
1408 32
                break;
1409
        }
1410
1411 420
        $this->addDeclaredProperty($property);
1412 420
    }
1413
1414
    /**
1415
     * INTERNAL:
1416
     * Adds a property mapping without completing/validating it.
1417
     * This is mainly used to add inherited property mappings to derived classes.
1418
     *
1419
     * @param Property $property
1420
     *
1421
     * @return void
1422
     */
1423 99
    public function addInheritedProperty(Property $property)
1424
    {
1425 99
        $inheritedProperty = clone $property;
1426 99
        $declaringClass    = $property->getDeclaringClass();
1427
1428 99
        if ($inheritedProperty instanceof FieldMetadata) {
1429 98
            if (! $declaringClass->isMappedSuperclass) {
1430 75
                $inheritedProperty->setTableName($property->getTableName());
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

1430
                $inheritedProperty->setTableName($property->/** @scrutinizer ignore-call */ getTableName());
Loading history...
1431
            }
1432
1433 98
            $this->fieldNames[$property->getColumnName()] = $property->getName();
1434 43
        } elseif ($inheritedProperty instanceof AssociationMetadata) {
1435 42
            if ($declaringClass->isMappedSuperclass) {
1436 10
                $inheritedProperty->setSourceEntity($this->className);
1437
            }
1438
1439
            // Need to add inherited fieldNames
1440 42
            if ($inheritedProperty instanceof ToOneAssociationMetadata && $inheritedProperty->isOwningSide()) {
1441 35
                foreach ($inheritedProperty->getJoinColumns() as $joinColumn) {
1442
                    /** @var JoinColumnMetadata $joinColumn */
1443 34
                    $this->fieldNames[$joinColumn->getColumnName()] = $property->getName();
1444
                }
1445
            }
1446
        }
1447
1448 99
        if (isset($this->declaredProperties[$property->getName()])) {
1449 1
            throw MappingException::duplicateProperty($this->className, $this->getProperty($property->getName()));
1450
        }
1451
1452 99
        $this->declaredProperties[$property->getName()] = $inheritedProperty;
1453
1454 99
        if ($inheritedProperty instanceof VersionFieldMetadata) {
1455 4
            $this->versionProperty = $inheritedProperty;
1456
        }
1457 99
    }
1458
1459
    /**
1460
     * INTERNAL:
1461
     * Adds a named query to this class.
1462
     *
1463
     * @param string $name
1464
     * @param string $query
1465
     *
1466
     * @return void
1467
     *
1468
     * @throws MappingException
1469
     */
1470 20
    public function addNamedQuery(string $name, string $query)
1471
    {
1472 20
        if (isset($this->namedQueries[$name])) {
1473 1
            throw MappingException::duplicateQueryMapping($this->className, $name);
1474
        }
1475
1476 20
        $this->namedQueries[$name] = $query;
1477 20
    }
1478
1479
    /**
1480
     * INTERNAL:
1481
     * Adds a named native query to this class.
1482
     *
1483
     * @param string $name
1484
     * @param string $query
1485
     * @param array  $queryMapping
1486
     *
1487
     * @return void
1488
     *
1489
     * @throws MappingException
1490
     */
1491 25
    public function addNamedNativeQuery(string $name, string $query, array $queryMapping)
1492
    {
1493 25
        if (isset($this->namedNativeQueries[$name])) {
1494 1
            throw MappingException::duplicateQueryMapping($this->className, $name);
1495
        }
1496
1497 25
        if (! isset($queryMapping['resultClass']) && ! isset($queryMapping['resultSetMapping'])) {
1498
            throw MappingException::missingQueryMapping($this->className, $name);
1499
        }
1500
1501 25
        $this->namedNativeQueries[$name] = array_merge(['query' => $query], $queryMapping);
1502 25
    }
1503
1504
    /**
1505
     * INTERNAL:
1506
     * Adds a sql result set mapping to this class.
1507
     *
1508
     * @param array $resultMapping
1509
     *
1510
     * @return void
1511
     *
1512
     * @throws MappingException
1513
     */
1514 25
    public function addSqlResultSetMapping(array $resultMapping)
1515
    {
1516 25
        if (!isset($resultMapping['name'])) {
1517
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->className);
1518
        }
1519
1520 25
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
1521 1
            throw MappingException::duplicateResultSetMapping($this->className, $resultMapping['name']);
1522
        }
1523
1524 25
        if (isset($resultMapping['entities'])) {
1525 25
            foreach ($resultMapping['entities'] as $key => $entityResult) {
1526 25
                if (! isset($entityResult['entityClass'])) {
1527 1
                    throw MappingException::missingResultSetMappingEntity($this->className, $resultMapping['name']);
1528
                }
1529
1530 24
                $entityClassName                                = $entityResult['entityClass'];
1531 24
                $resultMapping['entities'][$key]['entityClass'] = $entityClassName;
1532
1533 24
                if (isset($entityResult['fields'])) {
1534 20
                    foreach ($entityResult['fields'] as $k => $field) {
1535 20
                        if (! isset($field['name'])) {
1536
                            throw MappingException::missingResultSetMappingFieldName($this->className, $resultMapping['name']);
1537
                        }
1538
1539 20
                        if (! isset($field['column'])) {
1540 12
                            $fieldName = $field['name'];
1541
1542 12
                            if (strpos($fieldName, '.')) {
1543 6
                                list(, $fieldName) = explode('.', $fieldName);
1544
                            }
1545
1546 24
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
1547
                        }
1548
                    }
1549
                }
1550
            }
1551
        }
1552
1553 24
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
1554 24
    }
1555
1556
    /**
1557
     * Registers a custom repository class for the entity class.
1558
     *
1559
     * @param string|null $repositoryClassName The class name of the custom mapper.
1560
     *
1561
     * @return void
1562
     */
1563 32
    public function setCustomRepositoryClassName(?string $repositoryClassName)
1564
    {
1565 32
        $this->customRepositoryClassName = $repositoryClassName;
1566 32
    }
1567
1568
    /**
1569
     * @return string|null
1570
     */
1571 180
    public function getCustomRepositoryClassName() : ?string
1572
    {
1573 180
        return $this->customRepositoryClassName;
1574
    }
1575
1576
    /**
1577
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
1578
     *
1579
     * @param string $lifecycleEvent
1580
     *
1581
     * @return boolean
1582
     */
1583
    public function hasLifecycleCallbacks($lifecycleEvent)
1584
    {
1585
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
1586
    }
1587
1588
    /**
1589
     * Gets the registered lifecycle callbacks for an event.
1590
     *
1591
     * @param string $event
1592
     *
1593
     * @return array
1594
     */
1595
    public function getLifecycleCallbacks($event)
1596
    {
1597
        return $this->lifecycleCallbacks[$event] ?? [];
1598
    }
1599
1600
    /**
1601
     * Adds a lifecycle callback for entities of this class.
1602
     *
1603
     * @param string $callback
1604
     * @param string $event
1605
     *
1606
     * @return void
1607
     */
1608 19
    public function addLifecycleCallback($callback, $event)
1609
    {
1610 19
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
1611 3
            return;
1612
        }
1613
1614 19
        $this->lifecycleCallbacks[$event][] = $callback;
1615 19
    }
1616
1617
    /**
1618
     * Sets the lifecycle callbacks for entities of this class.
1619
     * Any previously registered callbacks are overwritten.
1620
     *
1621
     * @param array $callbacks
1622
     *
1623
     * @return void
1624
     */
1625 99
    public function setLifecycleCallbacks(array $callbacks) : void
1626
    {
1627 99
        $this->lifecycleCallbacks = $callbacks;
1628 99
    }
1629
1630
    /**
1631
     * Adds a entity listener for entities of this class.
1632
     *
1633
     * @param string $eventName The entity lifecycle event.
1634
     * @param string $class     The listener class.
1635
     * @param string $method    The listener callback method.
1636
     *
1637
     * @return void
1638
     *
1639
     * @throws \Doctrine\ORM\Mapping\MappingException
1640
     */
1641 19
    public function addEntityListener(string $eventName, string $class, string $method) : void
1642
    {
1643
        $listener = [
1644 19
            'class'  => $class,
1645 19
            'method' => $method,
1646
        ];
1647
1648 19
        if (! class_exists($class)) {
1649 1
            throw MappingException::entityListenerClassNotFound($class, $this->className);
1650
        }
1651
1652 18
        if (! method_exists($class, $method)) {
1653 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->className);
1654
        }
1655
1656 17
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) {
1657 1
            throw MappingException::duplicateEntityListener($class, $method, $this->className);
1658
        }
1659
1660 17
        $this->entityListeners[$eventName][] = $listener;
1661 17
    }
1662
1663
    /**
1664
     * Sets the discriminator column definition.
1665
     *
1666
     * @param DiscriminatorColumnMetadata $discriminatorColumn
1667
     *
1668
     * @return void
1669
     *
1670
     * @throws MappingException
1671
     *
1672
     * @see getDiscriminatorColumn()
1673
     */
1674 94
    public function setDiscriminatorColumn(DiscriminatorColumnMetadata $discriminatorColumn) : void
1675
    {
1676 94
        if (isset($this->fieldNames[$discriminatorColumn->getColumnName()])) {
1677 1
            throw MappingException::duplicateColumnName($this->className, $discriminatorColumn->getColumnName());
1678
        }
1679
1680 93
        $discriminatorColumn->setTableName($discriminatorColumn->getTableName() ?? $this->getTableName());
1681
1682 93
        $allowedTypeList = ['boolean', 'array', 'object', 'datetime', 'time', 'date'];
1683
1684 93
        if (in_array($discriminatorColumn->getTypeName(), $allowedTypeList, true)) {
1685
            throw MappingException::invalidDiscriminatorColumnType($discriminatorColumn->getTypeName());
1686
        }
1687
1688 93
        $this->discriminatorColumn = $discriminatorColumn;
1689 93
    }
1690
1691
    /**
1692
     * Sets the discriminator values used by this class.
1693
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
1694
     *
1695
     * @param array $map
1696
     *
1697
     * @return void
1698
     *
1699
     * @throws MappingException
1700
     */
1701 91
    public function setDiscriminatorMap(array $map) : void
1702
    {
1703 91
        foreach ($map as $value => $className) {
1704 91
            $this->addDiscriminatorMapClass($value, $className);
1705
        }
1706 91
    }
1707
1708
    /**
1709
     * Adds one entry of the discriminator map with a new class and corresponding name.
1710
     *
1711
     * @param string|int $name
1712
     * @param string     $className
1713
     *
1714
     * @return void
1715
     *
1716
     * @throws MappingException
1717
     */
1718 91
    public function addDiscriminatorMapClass($name, string $className) : void
1719
    {
1720 91
        $this->discriminatorMap[$name] = $className;
1721
1722 91
        if ($this->className === $className) {
1723 77
            $this->discriminatorValue = $name;
1724
1725 77
            return;
1726
        }
1727
1728 90
        if (! (class_exists($className) || interface_exists($className))) {
1729
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->className);
1730
        }
1731
1732 90
        if (is_subclass_of($className, $this->className) && ! in_array($className, $this->subClasses, true)) {
1733 85
            $this->subClasses[] = $className;
1734
        }
1735 90
    }
1736
1737
    /**
1738
     * @return ValueGenerationPlan
1739
     */
1740 1028
    public function getValueGenerationPlan() : ValueGenerationPlan
1741
    {
1742 1028
        return $this->valueGenerationPlan;
1743
    }
1744
1745
    /**
1746
     * @param ValueGenerationPlan $valueGenerationPlan
1747
     */
1748 364
    public function setValueGenerationPlan(ValueGenerationPlan $valueGenerationPlan) : void
1749
    {
1750 364
        $this->valueGenerationPlan = $valueGenerationPlan;
1751 364
    }
1752
1753
    /**
1754
     * Checks whether the class has a named query with the given query name.
1755
     *
1756
     * @param string $queryName
1757
     *
1758
     * @return boolean
1759
     */
1760 2
    public function hasNamedQuery($queryName) : bool
1761
    {
1762 2
        return isset($this->namedQueries[$queryName]);
1763
    }
1764
1765
    /**
1766
     * Checks whether the class has a named native query with the given query name.
1767
     *
1768
     * @param string $queryName
1769
     *
1770
     * @return boolean
1771
     */
1772 1
    public function hasNamedNativeQuery($queryName) : bool
1773
    {
1774 1
        return isset($this->namedNativeQueries[$queryName]);
1775
    }
1776
1777
    /**
1778
     * Checks whether the class has a named native query with the given query name.
1779
     *
1780
     * @param string $name
1781
     *
1782
     * @return boolean
1783
     */
1784 1
    public function hasSqlResultSetMapping($name) : bool
1785
    {
1786 1
        return isset($this->sqlResultSetMappings[$name]);
1787
    }
1788
1789
    /**
1790
     * Marks this class as read only, no change tracking is applied to it.
1791
     *
1792
     * @return void
1793
     */
1794 2
    public function asReadOnly() : void
1795
    {
1796 2
        $this->readOnly = true;
1797 2
    }
1798
1799
    /**
1800
     * Whether this class is read only or not.
1801
     *
1802
     * @return bool
1803
     */
1804 448
    public function isReadOnly() : bool
1805
    {
1806 448
        return $this->readOnly;
1807
    }
1808
1809
    /**
1810
     * @return bool
1811
     */
1812 1066
    public function isVersioned() : bool
1813
    {
1814 1066
        return $this->versionProperty !== null;
1815
    }
1816
1817
    /**
1818
     * Map Embedded Class
1819
     *
1820
     * @param array $mapping
1821
     *
1822
     * @throws MappingException
1823
     * @return void
1824
     */
1825 1
    public function mapEmbedded(array $mapping) : void
0 ignored issues
show
Unused Code introduced by
The parameter $mapping is not used and could be removed. ( Ignorable by Annotation )

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

1825
    public function mapEmbedded(/** @scrutinizer ignore-unused */ array $mapping) : void

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

Loading history...
1826
    {
1827
        /*if (isset($this->declaredProperties[$mapping['fieldName']])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
1828
            throw MappingException::duplicateProperty($this->className, $this->getProperty($mapping['fieldName']));
1829
        }
1830
1831
        $this->embeddedClasses[$mapping['fieldName']] = [
1832
            'class'          => $this->fullyQualifiedClassName($mapping['class']),
1833
            'columnPrefix'   => $mapping['columnPrefix'],
1834
            'declaredField'  => $mapping['declaredField'] ?? null,
1835
            'originalField'  => $mapping['originalField'] ?? null,
1836
            'declaringClass' => $this,
1837
        ];*/
1838 1
    }
1839
1840
    /**
1841
     * Inline the embeddable class
1842
     *
1843
     * @param string        $property
1844
     * @param ClassMetadata $embeddable
1845
     */
1846
    public function inlineEmbeddable($property, ClassMetadata $embeddable) : void
0 ignored issues
show
Unused Code introduced by
The parameter $property is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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

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

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

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

Loading history...
1847
    {
1848
        /*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...
1849
            $fieldMapping['fieldName']     = $property . "." . $fieldName;
1850
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->getClassName();
1851
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldName;
1852
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
1853
                ? $property . '.' . $fieldMapping['declaredField']
1854
                : $property;
1855
1856
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
1857
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
1858
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
1859
                $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName(
1860
                    $property,
1861
                    $fieldMapping['columnName'],
1862
                    $this->reflectionClass->getName(),
1863
                    $embeddable->reflectionClass->getName()
1864
                );
1865
            }
1866
1867
            $this->mapField($fieldMapping);
1868
        }*/
1869
    }
1870
}
1871