Failed Conditions
Pull Request — master (#7085)
by Guilherme
11:03
created

ClassMetadataFactory::getDriver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\DBAL\Platforms;
9
use Doctrine\DBAL\Platforms\AbstractPlatform;
10
use Doctrine\ORM\EntityManagerInterface;
11
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
12
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
13
use Doctrine\ORM\Events;
14
use Doctrine\ORM\ORMException;
15
use Doctrine\ORM\Sequencing;
16
use Doctrine\ORM\Sequencing\Planning\ColumnValueGeneratorExecutor;
17
use Doctrine\ORM\Sequencing\Planning\CompositeValueGenerationPlan;
18
use Doctrine\ORM\Sequencing\Planning\NoopValueGenerationPlan;
19
use Doctrine\ORM\Sequencing\Planning\SingleValueGenerationPlan;
20
use ReflectionException;
21
use function array_map;
22
use function class_exists;
23
use function count;
24
use function end;
25
use function explode;
26
use function is_subclass_of;
27
use function reset;
28
use function sprintf;
29
use function strpos;
30
use function strtolower;
31
use function var_export;
32
33
/**
34
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
35
 * metadata mapping information of a class which describes how a class should be mapped
36
 * to a relational database.
37
 */
38
class ClassMetadataFactory extends AbstractClassMetadataFactory
39
{
40
    /**
41
     * @var EntityManagerInterface|null
42
     */
43
    private $em;
44
45
    /**
46
     * @var AbstractPlatform
47
     */
48
    private $targetPlatform;
49
50
    /**
51
     * @var Driver\MappingDriver
52
     */
53
    private $driver;
54
55
    /**
56
     * @var EventManager
57
     */
58
    private $evm;
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 376
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
64
    {
65 376
        $loaded = parent::loadMetadata($name, $metadataBuildingContext);
66
67 357
        array_map([$this, 'resolveDiscriminatorValue'], $loaded);
68
69 357
        return $loaded;
70
    }
71
72 2259
    public function setEntityManager(EntityManagerInterface $em)
73
    {
74 2259
        $this->em = $em;
75 2259
    }
76
77
    /**
78
     * {@inheritdoc}
79
     *
80
     * @throws ORMException
81
     */
82 442
    protected function initialize() : void
83
    {
84 442
        $this->driver      = $this->em->getConfiguration()->getMetadataDriverImpl();
0 ignored issues
show
Bug introduced by
The method getConfiguration() does not exist on null. ( Ignorable by Annotation )

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

84
        $this->driver      = $this->em->/** @scrutinizer ignore-call */ getConfiguration()->getMetadataDriverImpl();

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...
85 442
        $this->evm         = $this->em->getEventManager();
86 442
        $this->initialized = true;
87 442
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 12
    protected function onNotFoundMetadata(
93
        string $className,
94
        ClassMetadataBuildingContext $metadataBuildingContext
95
    ) : ?ClassMetadata {
96 12
        if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
97 10
            return null;
98
        }
99
100 2
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $metadataBuildingContext, $this->em);
0 ignored issues
show
Bug introduced by
It seems like $this->em can also be of type null; however, parameter $entityManager of Doctrine\ORM\Event\OnCla...ventArgs::__construct() does only seem to accept Doctrine\ORM\EntityManagerInterface, 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

100
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $metadataBuildingContext, /** @scrutinizer ignore-type */ $this->em);
Loading history...
101
102 2
        $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
103
104 2
        return $eventArgs->getFoundMetadata();
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     *
110
     * @throws MappingException
111
     * @throws ORMException
112
     */
113 364
    protected function doLoadMetadata(
114
        string $className,
115
        ?ClassMetadata $parent,
116
        ClassMetadataBuildingContext $metadataBuildingContext
117
    ) : ClassMetadata {
118 364
        $classMetadata = new ClassMetadata($className, $metadataBuildingContext);
119
120 364
        if ($parent) {
121 98
            $classMetadata->setParent($parent);
122
123 98
            $this->addInheritedProperties($classMetadata, $parent);
124
125 98
            $classMetadata->setInheritanceType($parent->inheritanceType);
0 ignored issues
show
Bug introduced by
$parent->inheritanceType of type string is incompatible with the type integer expected by parameter $type of Doctrine\ORM\Mapping\Cla...a::setInheritanceType(). ( Ignorable by Annotation )

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

125
            $classMetadata->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
126 98
            $classMetadata->setIdentifier($parent->identifier);
127
128 98
            if ($parent->discriminatorColumn) {
129 72
                $classMetadata->setDiscriminatorColumn($parent->discriminatorColumn);
130 72
                $classMetadata->setDiscriminatorMap($parent->discriminatorMap);
131
            }
132
133 98
            $classMetadata->setLifecycleCallbacks($parent->lifecycleCallbacks);
134 98
            $classMetadata->setChangeTrackingPolicy($parent->changeTrackingPolicy);
135
136 98
            if ($parent->isMappedSuperclass) {
137 28
                $classMetadata->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
138
            }
139
        }
140
141
        // Invoke driver
142
        try {
143 364
            $this->driver->loadMetadataForClass($classMetadata->getClassName(), $classMetadata, $metadataBuildingContext);
144 3
        } catch (ReflectionException $e) {
145
            throw MappingException::reflectionFailure($classMetadata->getClassName(), $e);
146
        }
147
148 361
        $this->completeIdentifierGeneratorMappings($classMetadata);
149
150 361
        if ($parent) {
151 98
            if ($parent->getCache()) {
152 3
                $classMetadata->setCache(clone $parent->getCache());
153
            }
154
155 98
            if (! empty($parent->namedNativeQueries)) {
156 7
                $this->addInheritedNamedNativeQueries($classMetadata, $parent);
157
            }
158
159 98
            if (! empty($parent->sqlResultSetMappings)) {
160 7
                $this->addInheritedSqlResultSetMappings($classMetadata, $parent);
161
            }
162
163 98
            if (! empty($parent->entityListeners) && empty($classMetadata->entityListeners)) {
164 7
                $classMetadata->entityListeners = $parent->entityListeners;
165
            }
166
        }
167
168 361
        if (! $classMetadata->discriminatorMap && $classMetadata->inheritanceType !== InheritanceType::NONE && $classMetadata->isRootEntity()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classMetadata->discriminatorMap 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...
169 1
            $this->addDefaultDiscriminatorMap($classMetadata);
170
        }
171
172 361
        $this->completeRuntimeMetadata($classMetadata, $parent);
173
174 361
        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
175 6
            $eventArgs = new LoadClassMetadataEventArgs($classMetadata, $this->em);
0 ignored issues
show
Bug introduced by
It seems like $this->em can also be of type null; however, parameter $entityManager of Doctrine\ORM\Event\LoadC...ventArgs::__construct() does only seem to accept Doctrine\ORM\EntityManagerInterface, 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

175
            $eventArgs = new LoadClassMetadataEventArgs($classMetadata, /** @scrutinizer ignore-type */ $this->em);
Loading history...
176
177 6
            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
178
        }
179
180 360
        $this->buildValueGenerationPlan($classMetadata);
181 360
        $this->validateRuntimeMetadata($classMetadata, $parent);
182
183 358
        return $classMetadata;
184
    }
185
186 361
    protected function completeRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
187
    {
188 361
        if (! $parent || ! $parent->isMappedSuperclass) {
189 361
            return;
190
        }
191
192 28
        if ($class->isMappedSuperclass) {
193 1
            return;
194
        }
195
196 28
        $tableName = $class->getTableName();
197
198
        // Resolve column table names
199 28
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
200 28
            if ($property instanceof FieldMetadata) {
201 28
                $property->setTableName($property->getTableName() ?? $tableName);
202
203 28
                continue;
204
            }
205
206 12
            if (! ($property instanceof ToOneAssociationMetadata)) {
207 12
                continue;
208
            }
209
210
            // Resolve association join column table names
211 9
            foreach ($property->getJoinColumns() as $joinColumn) {
212
                /** @var JoinColumnMetadata $joinColumn */
213 9
                $joinColumn->setTableName($joinColumn->getTableName() ?? $tableName);
214
            }
215
        }
216 28
    }
217
218
    /**
219
     * Validate runtime metadata is correctly defined.
220
     *
221
     * @throws MappingException
222
     */
223 360
    protected function validateRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
224
    {
225 360
        if (! $class->getReflectionClass()) {
226
            // only validate if there is a reflection class instance
227
            return;
228
        }
229
230 360
        $class->validateIdentifier();
231 358
        $class->validateAssociations();
232 358
        $class->validateLifecycleCallbacks($this->getReflectionService());
233
234
        // verify inheritance
235 358
        if (! $class->isMappedSuperclass && $class->inheritanceType !== InheritanceType::NONE) {
236 75
            if (! $parent) {
237 74
                if (! $class->discriminatorMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->discriminatorMap 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...
238
                    throw MappingException::missingDiscriminatorMap($class->getClassName());
239
                }
240
241 74
                if (! $class->discriminatorColumn) {
242 75
                    throw MappingException::missingDiscriminatorColumn($class->getClassName());
243
                }
244
            }
245 322
        } elseif (($class->discriminatorMap || $class->discriminatorColumn) && $class->isMappedSuperclass && $class->isRootEntity()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->discriminatorMap 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...
246
            // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
247
            throw MappingException::noInheritanceOnMappedSuperClass($class->getClassName());
248
        }
249 358
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254 1958
    protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext
255
    {
256 1958
        return new ClassMetadataBuildingContext(
257 1958
            $this,
258 1958
            $this->getReflectionService(),
259 1958
            $this->em->getConfiguration()->getNamingStrategy()
260
        );
261
    }
262
263
    /**
264
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
265
     * map classes and looking for a fitting one.
266
     *
267
     * @throws \InvalidArgumentException
268
     * @throws \ReflectionException
269
     * @throws MappingException
270
     */
271 357
    private function resolveDiscriminatorValue(ClassMetadata $metadata) : void
272
    {
273 357
        if ($metadata->discriminatorValue || ! $metadata->discriminatorMap || $metadata->isMappedSuperclass ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->discriminatorMap 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...
274 357
            ! $metadata->getReflectionClass() || $metadata->getReflectionClass()->isAbstract()) {
275 357
            return;
276
        }
277
278
        // minor optimization: avoid loading related metadata when not needed
279 4
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
280 4
            if ($discriminatorClass === $metadata->getClassName()) {
281 3
                $metadata->discriminatorValue = $discriminatorValue;
282
283 4
                return;
284
            }
285
        }
286
287
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
288 1
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
289 1
            if ($metadata->getClassName() === $this->getMetadataFor($discriminatorClass)->getClassName()) {
290
                $metadata->discriminatorValue = $discriminatorValue;
291
292 1
                return;
293
            }
294
        }
295
296 1
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->getClassName(), $metadata->getRootClassName());
297
    }
298
299
    /**
300
     * Adds a default discriminator map if no one is given
301
     *
302
     * If an entity is of any inheritance type and does not contain a
303
     * discriminator map, then the map is generated automatically. This process
304
     * is expensive computation wise.
305
     *
306
     * The automatically generated discriminator map contains the lowercase short name of
307
     * each class as key.
308
     *
309
     * @throws MappingException
310
     */
311 1
    private function addDefaultDiscriminatorMap(ClassMetadata $class) : void
312
    {
313 1
        $allClasses = $this->driver->getAllClassNames();
314 1
        $fqcn       = $class->getClassName();
315 1
        $map        = [$this->getShortName($fqcn) => $fqcn];
316 1
        $duplicates = [];
317
318 1
        foreach ($allClasses as $subClassCandidate) {
319 1
            if (is_subclass_of($subClassCandidate, $fqcn)) {
320 1
                $shortName = $this->getShortName($subClassCandidate);
321
322 1
                if (isset($map[$shortName])) {
323
                    $duplicates[] = $shortName;
324
                }
325
326 1
                $map[$shortName] = $subClassCandidate;
327
            }
328
        }
329
330 1
        if ($duplicates) {
331
            throw MappingException::duplicateDiscriminatorEntry($class->getClassName(), $duplicates, $map);
332
        }
333
334 1
        $class->setDiscriminatorMap($map);
335 1
    }
336
337
    /**
338
     * Gets the lower-case short name of a class.
339
     *
340
     * @param string $className
341
     */
342 1
    private function getShortName($className) : string
343
    {
344 1
        if (strpos($className, '\\') === false) {
345
            return strtolower($className);
346
        }
347
348 1
        $parts = explode('\\', $className);
349
350 1
        return strtolower(end($parts));
351
    }
352
353
    /**
354
     * Adds inherited fields to the subclass mapping.
355
     *
356
     * @throws MappingException
357
     */
358 98
    private function addInheritedProperties(ClassMetadata $subClass, ClassMetadata $parentClass) : void
359
    {
360 98
        foreach ($parentClass->getDeclaredPropertiesIterator() as $fieldName => $property) {
361 97
            $subClass->addInheritedProperty($property);
362
        }
363 98
    }
364
365
    /**
366
     * Adds inherited named native queries to the subclass mapping.
367
     *
368
     * @throws MappingException
369
     */
370 7
    private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass) : void
371
    {
372 7
        foreach ($parentClass->namedNativeQueries as $name => $query) {
373 7
            if (isset($subClass->namedNativeQueries[$name])) {
374 4
                continue;
375
            }
376
377 7
            $subClass->addNamedNativeQuery(
378 7
                $name,
379 7
                $query['query'],
380
                [
381 7
                    'resultSetMapping' => $query['resultSetMapping'],
382 7
                    'resultClass'      => $query['resultClass'],
383
                ]
384
            );
385
        }
386 7
    }
387
388
    /**
389
     * Adds inherited sql result set mappings to the subclass mapping.
390
     *
391
     * @throws MappingException
392
     */
393 7
    private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass) : void
394
    {
395 7
        foreach ($parentClass->sqlResultSetMappings as $name => $mapping) {
396 7
            if (isset($subClass->sqlResultSetMappings[$name])) {
397 4
                continue;
398
            }
399
400 7
            $entities = [];
401
402 7
            foreach ($mapping['entities'] as $entity) {
403 7
                $entities[] = [
404 7
                    'fields'              => $entity['fields'],
405 7
                    'discriminatorColumn' => $entity['discriminatorColumn'],
406 7
                    'entityClass'         => $entity['entityClass'],
407
                ];
408
            }
409
410 7
            $subClass->addSqlResultSetMapping(
411
                [
412 7
                    'name'     => $mapping['name'],
413 7
                    'columns'  => $mapping['columns'],
414 7
                    'entities' => $entities,
415
                ]
416
            );
417
        }
418 7
    }
419
420
    /**
421
     * Completes the ID generator mapping. If "auto" is specified we choose the generator
422
     * most appropriate for the targeted database platform.
423
     *
424
     * @throws ORMException
425
     */
426 361
    private function completeIdentifierGeneratorMappings(ClassMetadata $class) : void
427
    {
428 361
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
429 359
            if (! $property instanceof FieldMetadata /*&& ! $property instanceof AssocationMetadata*/) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% 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...
430 248
                continue;
431
            }
432
433 357
            $this->completeFieldIdentifierGeneratorMapping($property);
434
        }
435 361
    }
436
437 357
    private function completeFieldIdentifierGeneratorMapping(FieldMetadata $field)
438
    {
439 357
        if (! $field->hasValueGenerator()) {
440 277
            return;
441
        }
442
443 287
        $platform  = $this->getTargetPlatform();
444 287
        $class     = $field->getDeclaringClass();
0 ignored issues
show
Unused Code introduced by
The assignment to $class is dead and can be removed.
Loading history...
445 287
        $generator = $field->getValueGenerator();
446
447 287
        if ($generator->getType() === GeneratorType::AUTO) {
448 277
            $generator = new ValueGeneratorMetadata(
449 277
                $platform->prefersSequences()
450
                    ? GeneratorType::SEQUENCE
451 277
                    : ($platform->prefersIdentityColumns()
452 277
                        ? GeneratorType::IDENTITY
453 277
                        : GeneratorType::TABLE
454
                ),
455 277
                $field->getValueGenerator()->getDefinition()
456
            );
457 277
            $field->setValueGenerator($generator);
458
        }
459
460
        // Validate generator definition and set defaults where needed
461 287
        switch ($generator->getType()) {
462 287
            case GeneratorType::SEQUENCE:
463
                // If there is no sequence definition yet, create a default definition
464 6
                if ($generator->getDefinition()) {
465 6
                    break;
466
                }
467
468
                // @todo guilhermeblanco Move sequence generation to DBAL
469
                $sequencePrefix = $platform->getSequencePrefix($field->getTableName(), $field->getSchemaName());
0 ignored issues
show
Bug introduced by
The method getSchemaName() does not exist on 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

469
                $sequencePrefix = $platform->getSequencePrefix($field->getTableName(), $field->/** @scrutinizer ignore-call */ getSchemaName());

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...
470
                $idSequenceName = sprintf('%s_%s_seq', $sequencePrefix, $field->getColumnName());
471
                $sequenceName   = $platform->fixSchemaElementName($idSequenceName);
472
473
                $field->setValueGenerator(
474
                    new ValueGeneratorMetadata(
475
                        $generator->getType(),
476
                        [
477
                            'sequenceName'   => $sequenceName,
478
                            'allocationSize' => 1,
479
                        ]
480
                    )
481
                );
482
483
                break;
484
485 281
            case GeneratorType::TABLE:
486
                throw new ORMException('TableGenerator not yet implemented.');
487
                break;
488
489 281
            case GeneratorType::CUSTOM:
490 1
                $definition = $generator->getDefinition();
491 1
                if (! isset($definition['class'])) {
492
                    throw new ORMException(sprintf('Cannot instantiate custom generator, no class has been defined'));
493
                }
494 1
                if (! class_exists($definition['class'])) {
495
                    throw new ORMException(sprintf('Cannot instantiate custom generator : %s', var_export($definition, true))); //$definition['class']));
0 ignored issues
show
Unused Code Comprehensibility introduced by
100% 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...
496
                }
497
498 1
                break;
499
500 280
            case GeneratorType::IDENTITY:
501
            case GeneratorType::NONE:
502
            case GeneratorType::UUID:
503 280
                break;
504
505
            default:
506
                throw new ORMException('Unknown generator type: ' . $generator->getType());
507
        }
508 287
    }
509
510
    /**
511
     * {@inheritDoc}
512
     */
513 198
    protected function getDriver() : Driver\MappingDriver
514
    {
515 198
        return $this->driver;
516
    }
517
518
    /**
519
     * {@inheritDoc}
520
     */
521
    protected function isEntity(ClassMetadata $class) : bool
522
    {
523
        return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
524
    }
525
526 287
    private function getTargetPlatform() : Platforms\AbstractPlatform
527
    {
528 287
        if (! $this->targetPlatform) {
529 287
            $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
530
        }
531
532 287
        return $this->targetPlatform;
533
    }
534
535 360
    private function buildValueGenerationPlan(ClassMetadata $class) : void
536
    {
537
        /** @var LocalColumnMetadata[] $generatedProperties */
538 360
        $generatedProperties = [];
539
540 360
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
541 358
            if (! ($property instanceof LocalColumnMetadata && $property->hasValueGenerator())) {
542 328
                continue;
543
            }
544
545 286
            $generatedProperties[] = $property;
546
        }
547
548 360
        switch (count($generatedProperties)) {
549 360
            case 0:
550 113
                $class->setValueGenerationPlan(new NoopValueGenerationPlan());
551 113
                break;
552
553 286
            case 1:
554 286
                $property = reset($generatedProperties);
555 286
                $executor = new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
556
557 286
                $class->setValueGenerationPlan(new SingleValueGenerationPlan($class, $executor));
558 286
                break;
559
560
            default:
561
                $executors = [];
562
563
                foreach ($generatedProperties as $property) {
564
                    $executors[] = new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
565
                }
566
567
                $class->setValueGenerationPlan(new CompositeValueGenerationPlan($class, $executors));
568
                break;
569
        }
570 360
    }
571
572 286
    private function createPropertyValueGenerator(
573
        ClassMetadata $class,
574
        LocalColumnMetadata $property
575
    ) : Sequencing\Generator {
576 286
        $platform = $this->getTargetPlatform();
577
578 286
        switch ($property->getValueGenerator()->getType()) {
579 286
            case GeneratorType::IDENTITY:
580 279
                $sequenceName = null;
581
582
                // Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
583 279
                if ($platform->usesSequenceEmulatedIdentityColumns()) {
584
                    $sequencePrefix = $platform->getSequencePrefix($class->getTableName(), $class->getSchemaName());
585
                    $idSequenceName = $platform->getIdentitySequenceName($sequencePrefix, $property->getColumnName());
586
                    $sequenceName   = $platform->quoteIdentifier($platform->fixSchemaElementName($idSequenceName));
587
                }
588
589 279
                return $property->getTypeName() === 'bigint'
590 1
                    ? new Sequencing\BigIntegerIdentityGenerator($sequenceName)
591 279
                    : new Sequencing\IdentityGenerator($sequenceName);
592
593 7
            case GeneratorType::SEQUENCE:
594 6
                $definition = $property->getValueGenerator()->getDefinition();
595 6
                return new Sequencing\SequenceGenerator(
596 6
                    $platform->quoteIdentifier($definition['sequenceName']),
597 6
                    $definition['allocationSize']
598
                );
599
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
600
601 1
            case GeneratorType::UUID:
602
                return new Sequencing\UuidGenerator();
603
                break;
604
605 1
            case GeneratorType::CUSTOM:
606 1
                $class = $property->getValueGenerator()->getDefinition()['class'];
607 1
                return new $class();
608
                break;
609
        }
610
    }
611
}
612