Failed Conditions
Push — master ( ee4e26...e98654 )
by Marco
13:06
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\DBAL\Platforms;
8
use Doctrine\ORM\EntityManagerInterface;
9
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
10
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
11
use Doctrine\ORM\Events;
12
use Doctrine\ORM\ORMException;
13
use Doctrine\ORM\Sequencing;
14
use Doctrine\ORM\Sequencing\Planning\ColumnValueGeneratorExecutor;
15
use Doctrine\ORM\Sequencing\Planning\CompositeValueGenerationPlan;
16
use Doctrine\ORM\Sequencing\Planning\NoopValueGenerationPlan;
17
use Doctrine\ORM\Sequencing\Planning\SingleValueGenerationPlan;
18
use ReflectionException;
19
20
/**
21
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
22
 * metadata mapping information of a class which describes how a class should be mapped
23
 * to a relational database.
24
 */
25
class ClassMetadataFactory extends AbstractClassMetadataFactory
26
{
27
    /**
28
     * @var EntityManagerInterface|null
29
     */
30
    private $em;
31
32
    /**
33
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
34
     */
35
    private $targetPlatform;
36
37
    /**
38
     * @var Driver\MappingDriver
39
     */
40
    private $driver;
41
42
    /**
43
     * @var \Doctrine\Common\EventManager
44
     */
45
    private $evm;
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 373
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
51
    {
52 373
        $loaded = parent::loadMetadata($name, $metadataBuildingContext);
53
54 354
        array_map([$this, 'resolveDiscriminatorValue'], $loaded);
55
56 354
        return $loaded;
57
    }
58
59 2251
    public function setEntityManager(EntityManagerInterface $em)
60
    {
61 2251
        $this->em = $em;
62 2251
    }
63
64
    /**
65
     * {@inheritdoc}
66
     *
67
     * @throws ORMException
68
     */
69 439
    protected function initialize() : void
70
    {
71 439
        $this->driver      = $this->em->getConfiguration()->getMetadataDriverImpl();
72 439
        $this->evm         = $this->em->getEventManager();
73 439
        $this->initialized = true;
74 439
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79 12
    protected function onNotFoundMetadata(
80
        string $className,
81
        ClassMetadataBuildingContext $metadataBuildingContext
82
    ) : ?ClassMetadata {
83 12
        if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
84 10
            return null;
85
        }
86
87 2
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $metadataBuildingContext, $this->em);
88
89 2
        $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
90
91 2
        return $eventArgs->getFoundMetadata();
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     *
97
     * @throws MappingException
98
     * @throws ORMException
99
     */
100 361
    protected function doLoadMetadata(
101
        string $className,
102
        ?ClassMetadata $parent,
103
        ClassMetadataBuildingContext $metadataBuildingContext
104
    ) : ClassMetadata {
105 361
        $classMetadata = new ClassMetadata($className, $metadataBuildingContext);
106
107 361
        if ($parent) {
108 98
            $classMetadata->setParent($parent);
109
110 98
            $this->addInheritedProperties($classMetadata, $parent);
111
112 97
            $classMetadata->setInheritanceType($parent->inheritanceType);
113 97
            $classMetadata->setIdentifier($parent->identifier);
114
115 97
            if ($parent->discriminatorColumn) {
116 71
                $classMetadata->setDiscriminatorColumn($parent->discriminatorColumn);
117 71
                $classMetadata->setDiscriminatorMap($parent->discriminatorMap);
118
            }
119
120 97
            $classMetadata->setLifecycleCallbacks($parent->lifecycleCallbacks);
121 97
            $classMetadata->setChangeTrackingPolicy($parent->changeTrackingPolicy);
122
123 97
            if ($parent->isMappedSuperclass) {
124 28
                $classMetadata->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
125
            }
126
        }
127
128
        // Invoke driver
129
        try {
130 361
            $this->driver->loadMetadataForClass($classMetadata->getClassName(), $classMetadata, $metadataBuildingContext);
131 2
        } catch (ReflectionException $e) {
132
            throw MappingException::reflectionFailure($classMetadata->getClassName(), $e);
133
        }
134
135 359
        $this->completeIdentifierGeneratorMappings($classMetadata);
136
137 359
        if ($parent) {
138 97
            if ($parent->getCache()) {
139 3
                $classMetadata->setCache(clone $parent->getCache());
140
            }
141
142 97
            if (! empty($parent->namedNativeQueries)) {
143 7
                $this->addInheritedNamedNativeQueries($classMetadata, $parent);
144
            }
145
146 97
            if (! empty($parent->sqlResultSetMappings)) {
147 7
                $this->addInheritedSqlResultSetMappings($classMetadata, $parent);
148
            }
149
150 97
            if (! empty($parent->entityListeners) && empty($classMetadata->entityListeners)) {
151 7
                $classMetadata->entityListeners = $parent->entityListeners;
152
            }
153
        }
154
155 359
        if (! $classMetadata->discriminatorMap && $classMetadata->inheritanceType !== InheritanceType::NONE && $classMetadata->isRootEntity()) {
156 1
            $this->addDefaultDiscriminatorMap($classMetadata);
157
        }
158
159 359
        $this->completeRuntimeMetadata($classMetadata, $parent);
160
161 359
        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
162 6
            $eventArgs = new LoadClassMetadataEventArgs($classMetadata, $this->em);
163
164 6
            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
165
        }
166
167 358
        $this->buildValueGenerationPlan($classMetadata);
168 358
        $this->validateRuntimeMetadata($classMetadata, $parent);
169
170 356
        return $classMetadata;
171
    }
172
173 359
    protected function completeRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
174
    {
175 359
        if (! $parent || ! $parent->isMappedSuperclass) {
176 359
            return;
177
        }
178
179 28
        if ($class->isMappedSuperclass) {
180 1
            return;
181
        }
182
183 28
        $tableName = $class->getTableName();
184
185
        // Resolve column table names
186 28
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
187 28
            if ($property instanceof FieldMetadata) {
188 28
                $property->setTableName($property->getTableName() ?? $tableName);
189
190 28
                continue;
191
            }
192
193 12
            if (! ($property instanceof ToOneAssociationMetadata)) {
194 12
                continue;
195
            }
196
197
            // Resolve association join column table names
198 9
            foreach ($property->getJoinColumns() as $joinColumn) {
199
                /** @var JoinColumnMetadata $joinColumn */
200 9
                $joinColumn->setTableName($joinColumn->getTableName() ?? $tableName);
201
            }
202
        }
203 28
    }
204
205
    /**
206
     * Validate runtime metadata is correctly defined.
207
     *
208
     * @throws MappingException
209
     */
210 358
    protected function validateRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
211
    {
212 358
        if (! $class->getReflectionClass()) {
213
            // only validate if there is a reflection class instance
214
            return;
215
        }
216
217 358
        $class->validateIdentifier();
218 356
        $class->validateAssociations();
219 356
        $class->validateLifecycleCallbacks($this->getReflectionService());
220
221
        // verify inheritance
222 356
        if (! $class->isMappedSuperclass && $class->inheritanceType !== InheritanceType::NONE) {
223 74
            if (! $parent) {
224 72
                if (! $class->discriminatorMap) {
225
                    throw MappingException::missingDiscriminatorMap($class->getClassName());
226
                }
227
228 72
                if (! $class->discriminatorColumn) {
229 74
                    throw MappingException::missingDiscriminatorColumn($class->getClassName());
230
                }
231
            }
232 321
        } elseif (($class->discriminatorMap || $class->discriminatorColumn) && $class->isMappedSuperclass && $class->isRootEntity()) {
233
            // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
234
            throw MappingException::noInheritanceOnMappedSuperClass($class->getClassName());
235
        }
236 356
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 1950
    protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext
242
    {
243 1950
        return new ClassMetadataBuildingContext(
244 1950
            $this,
245 1950
            $this->getReflectionService(),
246 1950
            $this->em->getConfiguration()->getNamingStrategy()
247
        );
248
    }
249
250
    /**
251
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
252
     * map classes and looking for a fitting one.
253
     *
254
     * @throws \InvalidArgumentException
255
     * @throws \ReflectionException
256
     * @throws MappingException
257
     */
258 354
    private function resolveDiscriminatorValue(ClassMetadata $metadata) : void
259
    {
260 354
        if ($metadata->discriminatorValue || ! $metadata->discriminatorMap || $metadata->isMappedSuperclass ||
261 354
            ! $metadata->getReflectionClass() || $metadata->getReflectionClass()->isAbstract()) {
262 354
            return;
263
        }
264
265
        // minor optimization: avoid loading related metadata when not needed
266 4
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
267 4
            if ($discriminatorClass === $metadata->getClassName()) {
268 3
                $metadata->discriminatorValue = $discriminatorValue;
269
270 4
                return;
271
            }
272
        }
273
274
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
275 1
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
276 1
            if ($metadata->getClassName() === $this->getMetadataFor($discriminatorClass)->getClassName()) {
277
                $metadata->discriminatorValue = $discriminatorValue;
278
279 1
                return;
280
            }
281
        }
282
283 1
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->getClassName(), $metadata->getRootClassName());
284
    }
285
286
    /**
287
     * Adds a default discriminator map if no one is given
288
     *
289
     * If an entity is of any inheritance type and does not contain a
290
     * discriminator map, then the map is generated automatically. This process
291
     * is expensive computation wise.
292
     *
293
     * The automatically generated discriminator map contains the lowercase short name of
294
     * each class as key.
295
     *
296
     * @throws MappingException
297
     */
298 1
    private function addDefaultDiscriminatorMap(ClassMetadata $class) : void
299
    {
300 1
        $allClasses = $this->driver->getAllClassNames();
301 1
        $fqcn       = $class->getClassName();
302 1
        $map        = [$this->getShortName($fqcn) => $fqcn];
303 1
        $duplicates = [];
304
305 1
        foreach ($allClasses as $subClassCandidate) {
306 1
            if (is_subclass_of($subClassCandidate, $fqcn)) {
307 1
                $shortName = $this->getShortName($subClassCandidate);
308
309 1
                if (isset($map[$shortName])) {
310
                    $duplicates[] = $shortName;
311
                }
312
313 1
                $map[$shortName] = $subClassCandidate;
314
            }
315
        }
316
317 1
        if ($duplicates) {
318
            throw MappingException::duplicateDiscriminatorEntry($class->getClassName(), $duplicates, $map);
319
        }
320
321 1
        $class->setDiscriminatorMap($map);
322 1
    }
323
324
    /**
325
     * Gets the lower-case short name of a class.
326
     *
327
     * @param string $className
328
     */
329 1
    private function getShortName($className) : string
330
    {
331 1
        if (strpos($className, '\\') === false) {
332
            return strtolower($className);
333
        }
334
335 1
        $parts = explode('\\', $className);
336
337 1
        return strtolower(end($parts));
338
    }
339
340
    /**
341
     * Adds inherited fields to the subclass mapping.
342
     *
343
     * @throws MappingException
344
     */
345 98
    private function addInheritedProperties(ClassMetadata $subClass, ClassMetadata $parentClass) : void
346
    {
347 98
        $isAbstract = $parentClass->isMappedSuperclass;
348
349 98
        foreach ($parentClass->getDeclaredPropertiesIterator() as $fieldName => $property) {
350 97
            if ($isAbstract && $property instanceof ToManyAssociationMetadata && ! $property->isOwningSide()) {
351 1
                throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->getClassName(), $fieldName);
352
            }
353
354 96
            $subClass->addInheritedProperty($property);
355
        }
356 97
    }
357
358
    /**
359
     * Adds inherited named native queries to the subclass mapping.
360
     *
361
     * @throws MappingException
362
     */
363 7
    private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass) : void
364
    {
365 7
        foreach ($parentClass->namedNativeQueries as $name => $query) {
366 7
            if (isset($subClass->namedNativeQueries[$name])) {
367 4
                continue;
368
            }
369
370 7
            $subClass->addNamedNativeQuery(
371 7
                $name,
372 7
                $query['query'],
373
                [
374 7
                    'resultSetMapping' => $query['resultSetMapping'],
375 7
                    'resultClass'      => $query['resultClass'],
376
                ]
377
            );
378
        }
379 7
    }
380
381
    /**
382
     * Adds inherited sql result set mappings to the subclass mapping.
383
     *
384
     * @throws MappingException
385
     */
386 7
    private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass) : void
387
    {
388 7
        foreach ($parentClass->sqlResultSetMappings as $name => $mapping) {
389 7
            if (isset($subClass->sqlResultSetMappings[$name])) {
390 4
                continue;
391
            }
392
393 7
            $entities = [];
394
395 7
            foreach ($mapping['entities'] as $entity) {
396 7
                $entities[] = [
397 7
                    'fields'              => $entity['fields'],
398 7
                    'discriminatorColumn' => $entity['discriminatorColumn'],
399 7
                    'entityClass'         => $entity['entityClass'],
400
                ];
401
            }
402
403 7
            $subClass->addSqlResultSetMapping(
404
                [
405 7
                    'name'     => $mapping['name'],
406 7
                    'columns'  => $mapping['columns'],
407 7
                    'entities' => $entities,
408
                ]
409
            );
410
        }
411 7
    }
412
413
    /**
414
     * Completes the ID generator mapping. If "auto" is specified we choose the generator
415
     * most appropriate for the targeted database platform.
416
     *
417
     * @throws ORMException
418
     */
419 359
    private function completeIdentifierGeneratorMappings(ClassMetadata $class) : void
420
    {
421 359
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
422 357
            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...
423 248
                continue;
424
            }
425
426 354
            $this->completeFieldIdentifierGeneratorMapping($property);
427
        }
428 359
    }
429
430 354
    private function completeFieldIdentifierGeneratorMapping(FieldMetadata $field)
431
    {
432 354
        if (! $field->hasValueGenerator()) {
433 274
            return;
434
        }
435
436 285
        $platform  = $this->getTargetPlatform();
437 285
        $class     = $field->getDeclaringClass();
438 285
        $generator = $field->getValueGenerator();
439
440 285
        if ($generator->getType() === GeneratorType::AUTO) {
441 274
            $generator = new ValueGeneratorMetadata(
442 274
                $platform->prefersSequences()
443
                    ? GeneratorType::SEQUENCE
444 274
                    : ($platform->prefersIdentityColumns()
445 274
                        ? GeneratorType::IDENTITY
446 274
                        : GeneratorType::TABLE
447
                ),
448 274
                $field->getValueGenerator()->getDefinition()
449
            );
450 274
            $field->setValueGenerator($generator);
451
        }
452
453
        // Validate generator definition and set defaults where needed
454 285
        switch ($generator->getType()) {
455 285
            case GeneratorType::SEQUENCE:
456
                // If there is no sequence definition yet, create a default definition
457 6
                if ($generator->getDefinition()) {
458 6
                    break;
459
                }
460
461
                // @todo guilhermeblanco Move sequence generation to DBAL
462
                $sequencePrefix = $platform->getSequencePrefix($field->getTableName(), $field->getSchemaName());
463
                $idSequenceName = sprintf('%s_%s_seq', $sequencePrefix, $field->getColumnName());
464
                $sequenceName   = $platform->fixSchemaElementName($idSequenceName);
465
466
                $field->setValueGenerator(
467
                    new ValueGeneratorMetadata(
468
                        $generator->getType(),
469
                        [
470
                            'sequenceName'   => $sequenceName,
471
                            'allocationSize' => 1,
472
                        ]
473
                    )
474
                );
475
476
                break;
477
478 279
            case GeneratorType::TABLE:
479
                throw new ORMException('TableGenerator not yet implemented.');
480
                break;
481
482 279
            case GeneratorType::CUSTOM:
483 1
                $definition = $generator->getDefinition();
484 1
                if (! isset($definition['class'])) {
485
                    throw new ORMException(sprintf('Cannot instantiate custom generator, no class has been defined'));
486
                }
487 1
                if (! class_exists($definition['class'])) {
488
                    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...
489
                }
490
491 1
                break;
492
493 278
            case GeneratorType::IDENTITY:
494
            case GeneratorType::NONE:
495
            case GeneratorType::UUID:
496 278
                break;
497
498
            default:
499
                throw new ORMException('Unknown generator type: ' . $generator->getType());
500
        }
501 285
    }
502
503
    /**
504
     * {@inheritDoc}
505
     */
506 196
    protected function getDriver() : Driver\MappingDriver
507
    {
508 196
        return $this->driver;
509
    }
510
511
    /**
512
     * {@inheritDoc}
513
     */
514
    protected function isEntity(ClassMetadata $class) : bool
515
    {
516
        return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
517
    }
518
519 285
    private function getTargetPlatform() : Platforms\AbstractPlatform
520
    {
521 285
        if (! $this->targetPlatform) {
522 285
            $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
523
        }
524
525 285
        return $this->targetPlatform;
526
    }
527
528 358
    private function buildValueGenerationPlan(ClassMetadata $class) : void
529
    {
530
        /** @var LocalColumnMetadata[] $generatedProperties */
531 358
        $generatedProperties = [];
532
533 358
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
534 356
            if (! ($property instanceof LocalColumnMetadata && $property->hasValueGenerator())) {
535 326
                continue;
536
            }
537
538 284
            $generatedProperties[] = $property;
539
        }
540
541 358
        switch (count($generatedProperties)) {
542 358
            case 0:
543 113
                $class->setValueGenerationPlan(new NoopValueGenerationPlan());
544 113
                break;
545
546 284
            case 1:
547 284
                $property = reset($generatedProperties);
548 284
                $executor = new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
549
550 284
                $class->setValueGenerationPlan(new SingleValueGenerationPlan($class, $executor));
551 284
                break;
552
553
            default:
554
                $executors = [];
555
556
                foreach ($generatedProperties as $property) {
557
                    $executors[] = new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
558
                }
559
560
                $class->setValueGenerationPlan(new CompositeValueGenerationPlan($class, $executors));
561
                break;
562
        }
563 358
    }
564
565 284
    private function createPropertyValueGenerator(
566
        ClassMetadata $class,
567
        LocalColumnMetadata $property
568
    ) : Sequencing\Generator {
569 284
        $platform = $this->getTargetPlatform();
570
571 284
        switch ($property->getValueGenerator()->getType()) {
572 284
            case GeneratorType::IDENTITY:
573 277
                $sequenceName = null;
574
575
                // Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
576 277
                if ($platform->usesSequenceEmulatedIdentityColumns()) {
577
                    $sequencePrefix = $platform->getSequencePrefix($class->getTableName(), $class->getSchemaName());
578
                    $idSequenceName = $platform->getIdentitySequenceName($sequencePrefix, $property->getColumnName());
579
                    $sequenceName   = $platform->quoteIdentifier($platform->fixSchemaElementName($idSequenceName));
580
                }
581
582 277
                return $property->getTypeName() === 'bigint'
583 1
                    ? new Sequencing\BigIntegerIdentityGenerator($sequenceName)
584 277
                    : new Sequencing\IdentityGenerator($sequenceName);
585
586 7
            case GeneratorType::SEQUENCE:
587 6
                $definition = $property->getValueGenerator()->getDefinition();
588 6
                return new Sequencing\SequenceGenerator(
589 6
                    $platform->quoteIdentifier($definition['sequenceName']),
590 6
                    $definition['allocationSize']
591
                );
592
                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...
593
594 1
            case GeneratorType::UUID:
595
                return new Sequencing\UuidGenerator();
596
                break;
597
598 1
            case GeneratorType::CUSTOM:
599 1
                $class = $property->getValueGenerator()->getDefinition()['class'];
600 1
                return new $class();
601
                break;
602
        }
603
    }
604
}
605