Failed Conditions
Push — master ( e747f7...5b15a6 )
by Guilherme
19:57
created

lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php (2 issues)

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\Exception\ORMException;
15
use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator;
16
use Doctrine\ORM\Mapping\Exception\TableGeneratorNotImplementedYet;
17
use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
18
use Doctrine\ORM\Sequencing;
19
use Doctrine\ORM\Sequencing\Planning\AssociationValueGeneratorExecutor;
20
use Doctrine\ORM\Sequencing\Planning\ColumnValueGeneratorExecutor;
21
use Doctrine\ORM\Sequencing\Planning\CompositeValueGenerationPlan;
22
use Doctrine\ORM\Sequencing\Planning\NoopValueGenerationPlan;
23
use Doctrine\ORM\Sequencing\Planning\SingleValueGenerationPlan;
24
use Doctrine\ORM\Sequencing\Planning\ValueGenerationExecutor;
25
use InvalidArgumentException;
26
use ReflectionException;
27
use function array_map;
28
use function class_exists;
29
use function count;
30
use function sprintf;
31
32
/**
33
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
34
 * metadata mapping information of a class which describes how a class should be mapped
35
 * to a relational database.
36
 */
37
class ClassMetadataFactory extends AbstractClassMetadataFactory
38
{
39
    /** @var EntityManagerInterface|null */
40
    private $em;
41
42
    /** @var AbstractPlatform */
43
    private $targetPlatform;
44
45
    /** @var Driver\MappingDriver */
46
    private $driver;
47
48
    /** @var EventManager */
49
    private $evm;
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 390
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
55
    {
56 390
        $loaded = parent::loadMetadata($name, $metadataBuildingContext);
57
58 365
        array_map([$this, 'resolveDiscriminatorValue'], $loaded);
59
60 365
        return $loaded;
61
    }
62
63 2280
    public function setEntityManager(EntityManagerInterface $em)
64
    {
65 2280
        $this->em = $em;
66 2280
    }
67
68
    /**
69
     * {@inheritdoc}
70
     *
71
     * @throws ORMException
72
     */
73 456
    protected function initialize() : void
74
    {
75 456
        $this->driver      = $this->em->getConfiguration()->getMetadataDriverImpl();
76 456
        $this->evm         = $this->em->getEventManager();
77 456
        $this->initialized = true;
78 456
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 12
    protected function onNotFoundMetadata(
84
        string $className,
85
        ClassMetadataBuildingContext $metadataBuildingContext
86
    ) : ?ClassMetadata {
87 12
        if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
88 10
            return null;
89
        }
90
91 2
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $metadataBuildingContext, $this->em);
92
93 2
        $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
94
95 2
        return $eventArgs->getFoundMetadata();
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     *
101
     * @throws MappingException
102
     * @throws ORMException
103
     */
104 378
    protected function doLoadMetadata(
105
        string $className,
106
        ?ClassMetadata $parent,
107
        ClassMetadataBuildingContext $metadataBuildingContext
108
    ) : ClassMetadata {
109 378
        $classMetadata = new ClassMetadata($className, $metadataBuildingContext);
110
111 378
        if ($parent) {
112 97
            $classMetadata->setParent($parent);
113
114 97
            foreach ($parent->getDeclaredPropertiesIterator() as $fieldName => $property) {
115 96
                $classMetadata->addInheritedProperty($property);
116
            }
117
118 97
            $classMetadata->setInheritanceType($parent->inheritanceType);
0 ignored issues
show
$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

118
            $classMetadata->setInheritanceType(/** @scrutinizer ignore-type */ $parent->inheritanceType);
Loading history...
119 97
            $classMetadata->setIdentifier($parent->identifier);
120
121 97
            if ($parent->discriminatorColumn) {
122 71
                $classMetadata->setDiscriminatorColumn($parent->discriminatorColumn);
123 71
                $classMetadata->setDiscriminatorMap($parent->discriminatorMap);
124
            }
125
126 97
            $classMetadata->setLifecycleCallbacks($parent->lifecycleCallbacks);
127 97
            $classMetadata->setChangeTrackingPolicy($parent->changeTrackingPolicy);
128
129 97
            if ($parent->isMappedSuperclass) {
130 27
                $classMetadata->setCustomRepositoryClassName($parent->getCustomRepositoryClassName());
131
            }
132
        }
133
134
        // Invoke driver
135
        try {
136 378
            $this->driver->loadMetadataForClass($classMetadata->getClassName(), $classMetadata, $metadataBuildingContext);
137 6
        } catch (ReflectionException $e) {
138
            throw MappingException::reflectionFailure($classMetadata->getClassName(), $e);
139
        }
140
141 372
        $this->completeIdentifierGeneratorMappings($classMetadata);
142
143 372
        if ($parent) {
144 97
            if ($parent->getCache()) {
145 3
                $classMetadata->setCache(clone $parent->getCache());
146
            }
147
148 97
            if (! empty($parent->entityListeners) && empty($classMetadata->entityListeners)) {
149 7
                $classMetadata->entityListeners = $parent->entityListeners;
150
            }
151
        }
152
153 372
        $this->completeRuntimeMetadata($classMetadata, $parent);
154
155 372
        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
156 6
            $eventArgs = new LoadClassMetadataEventArgs($classMetadata, $this->em);
157
158 6
            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
159
        }
160
161 371
        $this->buildValueGenerationPlan($classMetadata);
162 371
        $this->validateRuntimeMetadata($classMetadata, $parent);
163
164 366
        return $classMetadata;
165
    }
166
167 372
    protected function completeRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
168
    {
169 372
        if (! $parent || ! $parent->isMappedSuperclass) {
170 372
            return;
171
        }
172
173 27
        if ($class->isMappedSuperclass) {
174 1
            return;
175
        }
176
177 27
        $tableName = $class->getTableName();
178
179
        // Resolve column table names
180 27
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
181 27
            if ($property instanceof FieldMetadata) {
182 27
                $property->setTableName($property->getTableName() ?? $tableName);
183
184 27
                continue;
185
            }
186
187 12
            if (! ($property instanceof ToOneAssociationMetadata)) {
188 12
                continue;
189
            }
190
191
            // Resolve association join column table names
192 9
            foreach ($property->getJoinColumns() as $joinColumn) {
193
                /** @var JoinColumnMetadata $joinColumn */
194 9
                $joinColumn->setTableName($joinColumn->getTableName() ?? $tableName);
195
            }
196
        }
197 27
    }
198
199
    /**
200
     * Validate runtime metadata is correctly defined.
201
     *
202
     * @throws MappingException
203
     */
204 371
    protected function validateRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
205
    {
206 371
        if (! $class->getReflectionClass()) {
207
            // only validate if there is a reflection class instance
208
            return;
209
        }
210
211 371
        $class->validateIdentifier();
212 369
        $class->validateAssociations();
213 369
        $class->validateLifecycleCallbacks($this->getReflectionService());
214
215
        // verify inheritance
216 369
        if (! $class->isMappedSuperclass && $class->inheritanceType !== InheritanceType::NONE) {
217 77
            if (! $parent) {
218 76
                if (! $class->discriminatorMap) {
219 3
                    throw MappingException::missingDiscriminatorMap($class->getClassName());
220
                }
221
222 73
                if (! $class->discriminatorColumn) {
223 74
                    throw MappingException::missingDiscriminatorColumn($class->getClassName());
224
                }
225
            }
226 330
        } elseif (($class->discriminatorMap || $class->discriminatorColumn) && $class->isMappedSuperclass && $class->isRootEntity()) {
227
            // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
228
            throw MappingException::noInheritanceOnMappedSuperClass($class->getClassName());
229
        }
230 366
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235 1968
    protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext
236
    {
237 1968
        return new ClassMetadataBuildingContext(
238 1968
            $this,
239 1968
            $this->getReflectionService(),
240 1968
            $this->em->getConfiguration()->getNamingStrategy()
241
        );
242
    }
243
244
    /**
245
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
246
     * map classes and looking for a fitting one.
247
     *
248
     * @throws InvalidArgumentException
249
     * @throws ReflectionException
250
     * @throws MappingException
251
     */
252 365
    private function resolveDiscriminatorValue(ClassMetadata $metadata) : void
253
    {
254 365
        if ($metadata->discriminatorValue || ! $metadata->discriminatorMap || $metadata->isMappedSuperclass ||
255 365
            ! $metadata->getReflectionClass() || $metadata->getReflectionClass()->isAbstract()) {
256 365
            return;
257
        }
258
259
        // minor optimization: avoid loading related metadata when not needed
260 4
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
261 4
            if ($discriminatorClass === $metadata->getClassName()) {
262 3
                $metadata->discriminatorValue = $discriminatorValue;
263
264 3
                return;
265
            }
266
        }
267
268
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
269 1
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
270 1
            if ($metadata->getClassName() === $this->getMetadataFor($discriminatorClass)->getClassName()) {
271
                $metadata->discriminatorValue = $discriminatorValue;
272
273
                return;
274
            }
275
        }
276
277 1
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->getClassName(), $metadata->getRootClassName());
278
    }
279
280
    /**
281
     * Completes the ID generator mapping. If "auto" is specified we choose the generator
282
     * most appropriate for the targeted database platform.
283
     *
284
     * @throws ORMException
285
     */
286 372
    private function completeIdentifierGeneratorMappings(ClassMetadata $class) : void
287
    {
288 372
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
289 370
            if (! $property instanceof FieldMetadata /*&& ! $property instanceof AssocationMetadata*/) {
290 253
                continue;
291
            }
292
293 368
            $this->completeFieldIdentifierGeneratorMapping($property);
294
        }
295 372
    }
296
297 368
    private function completeFieldIdentifierGeneratorMapping(FieldMetadata $field)
298
    {
299 368
        if (! $field->hasValueGenerator()) {
300 283
            return;
301
        }
302
303 295
        $platform  = $this->getTargetPlatform();
304 295
        $class     = $field->getDeclaringClass();
305 295
        $generator = $field->getValueGenerator();
306
307 295
        if ($generator->getType() === GeneratorType::AUTO) {
308 285
            $generator = new ValueGeneratorMetadata(
309 285
                $platform->prefersSequences()
310
                    ? GeneratorType::SEQUENCE
311 285
                    : ($platform->prefersIdentityColumns()
312 285
                        ? GeneratorType::IDENTITY
313 285
                        : GeneratorType::TABLE
314
                ),
315 285
                $field->getValueGenerator()->getDefinition()
316
            );
317 285
            $field->setValueGenerator($generator);
318
        }
319
320
        // Validate generator definition and set defaults where needed
321 295
        switch ($generator->getType()) {
322 295
            case GeneratorType::SEQUENCE:
323
                // If there is no sequence definition yet, create a default definition
324 6
                if ($generator->getDefinition()) {
325 6
                    break;
326
                }
327
328
                // @todo guilhermeblanco Move sequence generation to DBAL
329
                $sequencePrefix = $platform->getSequencePrefix($field->getTableName(), $field->getSchemaName());
330
                $idSequenceName = sprintf('%s_%s_seq', $sequencePrefix, $field->getColumnName());
331
                $sequenceName   = $platform->fixSchemaElementName($idSequenceName);
332
333
                $field->setValueGenerator(
334
                    new ValueGeneratorMetadata(
335
                        $generator->getType(),
336
                        [
337
                            'sequenceName'   => $sequenceName,
338
                            'allocationSize' => 1,
339
                        ]
340
                    )
341
                );
342
343
                break;
344
345 289
            case GeneratorType::TABLE:
346
                throw TableGeneratorNotImplementedYet::create();
347
                break;
348
349 289
            case GeneratorType::CUSTOM:
350 1
                $definition = $generator->getDefinition();
351 1
                if (! isset($definition['class'])) {
352
                    throw InvalidCustomGenerator::onClassNotConfigured();
353
                }
354 1
                if (! class_exists($definition['class'])) {
355
                    throw InvalidCustomGenerator::onMissingClass($definition);
356
                }
357
358 1
                break;
359
360 288
            case GeneratorType::IDENTITY:
361
            case GeneratorType::NONE:
362 288
                break;
363
364
            default:
365
                throw UnknownGeneratorType::create($generator->getType());
366
        }
367 295
    }
368
369
    /**
370
     * {@inheritDoc}
371
     */
372 199
    protected function getDriver() : Driver\MappingDriver
373
    {
374 199
        return $this->driver;
375
    }
376
377
    /**
378
     * {@inheritDoc}
379
     */
380
    protected function isEntity(ClassMetadata $class) : bool
381
    {
382
        return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
383
    }
384
385 295
    private function getTargetPlatform() : Platforms\AbstractPlatform
386
    {
387 295
        if (! $this->targetPlatform) {
388 295
            $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
389
        }
390
391 295
        return $this->targetPlatform;
392
    }
393
394 371
    private function buildValueGenerationPlan(ClassMetadata $class) : void
395
    {
396 371
        $executors = $this->buildValueGenerationExecutorList($class);
397
398 371
        switch (count($executors)) {
399 371
            case 0:
400 92
                $class->setValueGenerationPlan(new NoopValueGenerationPlan());
401 92
                break;
402
403 310
            case 1:
404 305
                $class->setValueGenerationPlan(new SingleValueGenerationPlan($class, $executors[0]));
405 305
                break;
406
407
            default:
408 18
                $class->setValueGenerationPlan(new CompositeValueGenerationPlan($class, $executors));
409 18
                break;
410
        }
411 371
    }
412
413
    /**
414
     * @return ValueGenerationExecutor[]
415
     */
416 371
    private function buildValueGenerationExecutorList(ClassMetadata $class) : array
417
    {
418 371
        $executors = [];
419
420 371
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
421 369
            $executor = $this->buildValueGenerationExecutorForProperty($class, $property);
422
423 369
            if ($executor instanceof ValueGenerationExecutor) {
424 310
                $executors[] = $executor;
425
            }
426
        }
427
428 371
        return $executors;
429
    }
430
431 369
    private function buildValueGenerationExecutorForProperty(
432
        ClassMetadata $class,
433
        Property $property
434
    ) : ?ValueGenerationExecutor {
435 369
        if ($property instanceof LocalColumnMetadata && $property->hasValueGenerator()) {
436 294
            return new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property));
437
        }
438
439 335
        if ($property instanceof ToOneAssociationMetadata && $property->isPrimaryKey()) {
440 41
            return new AssociationValueGeneratorExecutor();
441
        }
442
443 331
        return null;
444
    }
445
446 294
    private function createPropertyValueGenerator(
447
        ClassMetadata $class,
448
        LocalColumnMetadata $property
449
    ) : Sequencing\Generator {
450 294
        $platform = $this->getTargetPlatform();
451
452 294
        switch ($property->getValueGenerator()->getType()) {
453 294
            case GeneratorType::IDENTITY:
454 287
                $sequenceName = null;
455
456
                // Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
457 287
                if ($platform->usesSequenceEmulatedIdentityColumns()) {
458
                    $sequencePrefix = $platform->getSequencePrefix($class->getTableName(), $class->getSchemaName());
459
                    $idSequenceName = $platform->getIdentitySequenceName($sequencePrefix, $property->getColumnName());
460
                    $sequenceName   = $platform->quoteIdentifier($platform->fixSchemaElementName($idSequenceName));
461
                }
462
463 287
                return $property->getTypeName() === 'bigint'
464 1
                    ? new Sequencing\BigIntegerIdentityGenerator($sequenceName)
465 287
                    : new Sequencing\IdentityGenerator($sequenceName);
466 7
            case GeneratorType::SEQUENCE:
467 6
                $definition = $property->getValueGenerator()->getDefinition();
468
469 6
                return new Sequencing\SequenceGenerator(
470 6
                    $platform->quoteIdentifier($definition['sequenceName']),
471 6
                    $definition['allocationSize']
472
                );
473 1
            case GeneratorType::CUSTOM:
474 1
                $class = $property->getValueGenerator()->getDefinition()['class'];
475
476 1
                return new $class();
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Doctrine\ORM\Sequencing\Generator. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
477
        }
478
    }
479
}
480