Failed Conditions
Push — master ( fa7802...d60694 )
by Guilherme
09:27
created

ClassMetadataFactory::completeFieldIdentifierGeneratorMapping()   C

Complexity

Conditions 12
Paths 64

Size

Total Lines 56
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 21.6512

Importance

Changes 0
Metric Value
cc 12
eloc 33
nc 64
nop 1
dl 0
loc 56
ccs 19
cts 32
cp 0.5938
crap 21.6512
rs 6.9666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping;
6
7
use Doctrine\Common\Cache\Cache;
8
use Doctrine\Common\EventManager;
9
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory as PersistenceClassMetadataFactory;
10
use Doctrine\Common\Persistence\Mapping\MappingException as PersistenceMappingException;
11
use Doctrine\DBAL\Platforms;
12
use Doctrine\DBAL\Platforms\AbstractPlatform;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
15
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
16
use Doctrine\ORM\Events;
17
use Doctrine\ORM\Exception\ORMException;
18
use Doctrine\ORM\Mapping\Exception\TableGeneratorNotImplementedYet;
0 ignored issues
show
introduced by
Type Doctrine\ORM\Mapping\Exception\TableGeneratorNotImplementedYet is not used in this file.
Loading history...
19
use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
0 ignored issues
show
introduced by
Type Doctrine\ORM\Mapping\Exception\UnknownGeneratorType is not used in this file.
Loading history...
20
use Doctrine\ORM\Reflection\ReflectionService;
21
use Doctrine\ORM\Reflection\RuntimeReflectionService;
22
use Doctrine\ORM\Sequencing\Planning\CompositeValueGenerationPlan;
23
use Doctrine\ORM\Sequencing\Planning\NoopValueGenerationPlan;
24
use Doctrine\ORM\Sequencing\Planning\SingleValueGenerationPlan;
25
use Doctrine\ORM\Sequencing\Executor\ValueGenerationExecutor;
0 ignored issues
show
Coding Style introduced by
Use statements should be sorted alphabetically. The first wrong one is Doctrine\ORM\Sequencing\Executor\ValueGenerationExecutor.
Loading history...
26
use Doctrine\ORM\Utility\StaticClassNameConverter;
27
use InvalidArgumentException;
28
use ReflectionException;
29
use function array_map;
30
use function array_reverse;
31
use function count;
32
use function in_array;
0 ignored issues
show
introduced by
Type in_array is not used in this file.
Loading history...
33
use function reset;
34
use function sprintf;
0 ignored issues
show
introduced by
Type sprintf is not used in this file.
Loading history...
35
36
/**
37
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
38
 * metadata mapping information of a class which describes how a class should be mapped
39
 * to a relational database.
40
 */
41
class ClassMetadataFactory implements PersistenceClassMetadataFactory
42
{
43
    /**
44
     * Salt used by specific Object Manager implementation.
45
     *
46
     * @var string
47
     */
48
    protected $cacheSalt = '$CLASSMETADATA';
49
50
    /** @var bool */
51
    protected $initialized = false;
52
53
    /** @var ReflectionService|null */
54
    protected $reflectionService;
55
56
    /** @var EntityManagerInterface|null */
57
    private $em;
58
59
    /** @var AbstractPlatform */
60
    private $targetPlatform;
61
62
    /** @var Driver\MappingDriver */
63
    private $driver;
64
65
    /** @var EventManager */
66
    private $evm;
67
68
    /** @var Cache|null */
69
    private $cacheDriver;
70
71
    /** @var ClassMetadata[] */
72
    private $loadedMetadata = [];
73
74
    /**
75
     * Sets the entity manager used to build ClassMetadata instances.
76
     */
77 2275
    public function setEntityManager(EntityManagerInterface $em)
78
    {
79 2275
        $this->em = $em;
80 2275
    }
81
82
    /**
83
     * Sets the cache driver used by the factory to cache ClassMetadata instances.
84
     */
85 2273
    public function setCacheDriver(?Cache $cacheDriver = null) : void
86
    {
87 2273
        $this->cacheDriver = $cacheDriver;
88 2273
    }
89
90
    /**
91
     * Gets the cache driver used by the factory to cache ClassMetadata instances.
92
     */
93
    public function getCacheDriver() : ?Cache
94
    {
95
        return $this->cacheDriver;
96
    }
97
98
    /**
99
     * Returns an array of all the loaded metadata currently in memory.
100
     *
101
     * @return ClassMetadata[]
102
     */
103
    public function getLoadedMetadata() : array
104
    {
105
        return $this->loadedMetadata;
106
    }
107
108
    /**
109
     * Sets the reflectionService.
110
     */
111
    public function setReflectionService(ReflectionService $reflectionService) : void
112
    {
113
        $this->reflectionService = $reflectionService;
114
    }
115
116
    /**
117
     * Gets the reflection service associated with this metadata factory.
118
     */
119 1963
    public function getReflectionService() : ReflectionService
120
    {
121 1963
        if ($this->reflectionService === null) {
122 1963
            $this->reflectionService = new RuntimeReflectionService();
123
        }
124
125 1963
        return $this->reflectionService;
126
    }
127
128
    /**
129
     * {@inheritDoc}
130
     */
131 48
    public function isTransient($className) : bool
132
    {
133 48
        if (! $this->initialized) {
134 15
            $this->initialize();
135
        }
136
137 48
        $entityClassName = StaticClassNameConverter::getRealClass($className);
138
139 48
        return $this->getDriver()->isTransient($entityClassName);
140
    }
141
142
    /**
143
     * Checks whether the factory has the metadata for a class loaded already.
144
     *
145
     * @param string $className
146
     *
147
     * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
148
     */
149 66
    public function hasMetadataFor($className) : bool
150
    {
151 66
        return isset($this->loadedMetadata[$className]);
152
    }
153
154
    /**
155
     * Sets the metadata descriptor for a specific class.
156
     *
157
     * NOTE: This is only useful in very special cases, like when generating proxy classes.
158
     *
159
     * @param string        $className
160
     * @param ClassMetadata $class
161
     */
162 5
    public function setMetadataFor($className, $class) : void
163
    {
164 5
        $this->loadedMetadata[$className] = $class;
165 5
    }
166
167
    /**
168
     * Forces the factory to load the metadata of all classes known to the underlying
169
     * mapping driver.
170
     *
171
     * @return ClassMetadata[] The ClassMetadata instances of all mapped classes.
172
     *
173
     * @throws InvalidArgumentException
174
     * @throws ReflectionException
175
     * @throws MappingException
176
     * @throws PersistenceMappingException
177
     * @throws ORMException
178
     */
179 56
    public function getAllMetadata() : array
180
    {
181 56
        if (! $this->initialized) {
182 56
            $this->initialize();
183
        }
184
185 56
        $driver   = $this->getDriver();
186 56
        $metadata = [];
187
188 56
        foreach ($driver->getAllClassNames() as $className) {
189 55
            $metadata[] = $this->getMetadataFor($className);
190
        }
191
192 56
        return $metadata;
0 ignored issues
show
introduced by
The expression return $metadata returns an array which contains values of type Doctrine\ORM\Mapping\ClassMetadata which are incompatible with the return type Doctrine\Common\Persistence\Mapping\ClassMetadata mandated by Doctrine\Common\Persiste...ctory::getAllMetadata().
Loading history...
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     *
198
     * @throws ORMException
199
     */
200 451
    protected function initialize() : void
201
    {
202 451
        $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

202
        $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...
203 451
        $this->evm         = $this->em->getEventManager();
204 451
        $this->initialized = true;
205 451
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 198
    protected function getDriver() : Driver\MappingDriver
211
    {
212 198
        return $this->driver;
213
    }
214
215 1962
    public function getTargetPlatform() : Platforms\AbstractPlatform
216
    {
217 1962
        if (! $this->targetPlatform) {
218 1962
            $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
219
        }
220
221 1962
        return $this->targetPlatform;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    protected function isEntity(ClassMetadata $class) : bool
228
    {
229
        return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
230
    }
231
232
    /**
233
     * Gets the class metadata descriptor for a class.
234
     *
235
     * @param string $className The name of the class.
236
     *
237
     * @throws InvalidArgumentException
238
     * @throws PersistenceMappingException
239
     * @throws ORMException
240
     */
241 1968
    public function getMetadataFor($className) : ClassMetadata
242
    {
243 1968
        if (isset($this->loadedMetadata[$className])) {
244 1646
            return $this->loadedMetadata[$className];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->loadedMetadata[$className] returns the type Doctrine\ORM\Mapping\ClassMetadata which is incompatible with the return type mandated by Doctrine\Common\Persiste...ctory::getMetadataFor() of Doctrine\Common\Persistence\Mapping\ClassMetadata.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
245
        }
246
247 1963
        $entityClassName = StaticClassNameConverter::getRealClass($className);
248
249 1963
        if (isset($this->loadedMetadata[$entityClassName])) {
250
            // We do not have the alias name in the map, include it
251 214
            return $this->loadedMetadata[$className] = $this->loadedMetadata[$entityClassName];
252
        }
253
254 1963
        $metadataBuildingContext = $this->newClassMetadataBuildingContext();
255 1963
        $loadingException        = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $loadingException is dead and can be removed.
Loading history...
256
257
        try {
258 1963
            if ($this->cacheDriver) {
259 1859
                $cached = $this->cacheDriver->fetch($entityClassName . $this->cacheSalt);
260
261 1859
                if ($cached instanceof ClassMetadata) {
262 1632
                    $this->loadedMetadata[$entityClassName] = $cached;
263
264 1632
                    $cached->wakeupReflection($metadataBuildingContext->getReflectionService());
265
                } else {
266 1859
                    foreach ($this->loadMetadata($entityClassName, $metadataBuildingContext) as $loadedClass) {
267 267
                        $loadedClassName = $loadedClass->getClassName();
268
269 267
                        $this->cacheDriver->save($loadedClassName . $this->cacheSalt, $loadedClass, null);
270
                    }
271
                }
272
            } else {
273 1955
                $this->loadMetadata($entityClassName, $metadataBuildingContext);
274
            }
275 24
        } catch (PersistenceMappingException $loadingException) {
276 12
            $fallbackMetadataResponse = $this->onNotFoundMetadata($entityClassName, $metadataBuildingContext);
277
278 12
            if (! $fallbackMetadataResponse) {
279 10
                throw $loadingException;
280
            }
281
282 2
            $this->loadedMetadata[$entityClassName] = $fallbackMetadataResponse;
283
        }
284
285 1947
        if ($className !== $entityClassName) {
286
            // We do not have the alias name in the map, include it
287 3
            $this->loadedMetadata[$className] = $this->loadedMetadata[$entityClassName];
288
        }
289
290 1947
        $metadataBuildingContext->validate();
291
292 1947
        return $this->loadedMetadata[$entityClassName];
293
    }
294
295
    /**
296
     * Loads the metadata of the class in question and all it's ancestors whose metadata
297
     * is still not loaded.
298
     *
299
     * Important: The class $name does not necessarily exist at this point here.
300
     * Scenarios in a code-generation setup might have access to XML
301
     * Mapping files without the actual PHP code existing here. That is why the
302
     * {@see Doctrine\ORM\Reflection\ReflectionService} interface
303
     * should be used for reflection.
304
     *
305
     * @param string $name The name of the class for which the metadata should get loaded.
306
     *
307
     * @return ClassMetadata[]
308
     *
309
     * @throws InvalidArgumentException
310
     * @throws ORMException
311
     */
312 385
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
313
    {
314 385
        if (! $this->initialized) {
315 380
            $this->initialize();
316
        }
317
318 385
        $loaded          = [];
319 385
        $parentClasses   = $this->getParentClasses($name);
320 374
        $parentClasses[] = $name;
321
322
        // Move down the hierarchy of parent classes, starting from the topmost class
323 374
        $parent = null;
324
325 374
        foreach ($parentClasses as $className) {
326 374
            if (isset($this->loadedMetadata[$className])) {
327 71
                $parent = $this->loadedMetadata[$className];
328
329 71
                continue;
330
            }
331
332 374
            $class = $this->doLoadMetadata($className, $parent, $metadataBuildingContext);
333
334 365
            $this->loadedMetadata[$className] = $class;
335
336 365
            $parent   = $class;
337 365
            $loaded[] = $class;
338
        }
339
340 363
        array_map([$this, 'resolveDiscriminatorValue'], $loaded);
341
342 363
        return $loaded;
343
    }
344
345
    /**
346
     * Gets an array of parent classes for the given entity class.
347
     *
348
     * @param string $name
349
     *
350
     * @return string[]
351
     *
352
     * @throws InvalidArgumentException
353
     */
354 385
    protected function getParentClasses($name) : array
355
    {
356
        // Collect parent classes, ignoring transient (not-mapped) classes.
357 385
        $parentClasses = [];
358
359 385
        foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
360 104
            if (! $this->getDriver()->isTransient($parentClass)) {
361 99
                $parentClasses[] = $parentClass;
362
            }
363
        }
364
365 374
        return $parentClasses;
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371 12
    protected function onNotFoundMetadata(
372
        string $className,
373
        ClassMetadataBuildingContext $metadataBuildingContext
374
    ) : ?ClassMetadata {
375 12
        if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
376 10
            return null;
377
        }
378
379 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

379
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $metadataBuildingContext, /** @scrutinizer ignore-type */ $this->em);
Loading history...
380
381 2
        $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
382
383 2
        return $eventArgs->getFoundMetadata();
384
    }
385
386
    /**
387
     * {@inheritdoc}
388
     *
389
     * @throws MappingException
390
     * @throws ORMException
391
     */
392 373
    protected function doLoadMetadata(
393
        string $className,
394
        ?ClassMetadata $parent,
395
        ClassMetadataBuildingContext $metadataBuildingContext
396
    ) : ?ComponentMetadata {
397
        /** @var ClassMetadata $classMetadata */
398 373
        $classMetadata = $this->driver->loadMetadataForClass($className, $parent, $metadataBuildingContext);
399
400 367
        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
401 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

401
            $eventArgs = new LoadClassMetadataEventArgs($classMetadata, /** @scrutinizer ignore-type */ $this->em);
Loading history...
402
403 6
            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
404
        }
405
406 367
        $this->buildValueGenerationPlan($classMetadata);
407 367
        $this->validateRuntimeMetadata($classMetadata, $parent);
408
409 364
        return $classMetadata;
410
    }
411
412
    /**
413
     * Validate runtime metadata is correctly defined.
414
     *
415
     * @throws MappingException
416
     */
417 367
    protected function validateRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void
418
    {
419 367
        if (! $class->getReflectionClass()) {
420
            // only validate if there is a reflection class instance
421
            return;
422
        }
423
424 367
        $class->validateIdentifier();
425 367
        $class->validateAssociations();
426 367
        $class->validateLifecycleCallbacks($this->getReflectionService());
427
428
        // verify inheritance
429 367
        if (! $class->isMappedSuperclass && $class->inheritanceType !== InheritanceType::NONE) {
430 76
            if (! $parent) {
431 75
                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...
432 3
                    throw MappingException::missingDiscriminatorMap($class->getClassName());
433
                }
434
435 72
                if (! $class->discriminatorColumn) {
436 73
                    throw MappingException::missingDiscriminatorColumn($class->getClassName());
437
                }
438
            }
439 329
        } 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...
440
            // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
441
            throw MappingException::noInheritanceOnMappedSuperClass($class->getClassName());
442
        }
443 364
    }
444
445
    /**
446
     * {@inheritdoc}
447
     */
448 1963
    protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext
449
    {
450 1963
        return new ClassMetadataBuildingContext(
451 1963
            $this,
452 1963
            $this->getReflectionService(),
453 1963
            $this->getTargetPlatform(),
454 1963
            $this->em->getConfiguration()->getNamingStrategy()
455
        );
456
    }
457
458
    /**
459
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
460
     * map classes and looking for a fitting one.
461
     *
462
     * @throws InvalidArgumentException
463
     * @throws ReflectionException
464
     * @throws MappingException
465
     * @throws PersistenceMappingException
466
     */
467 363
    private function resolveDiscriminatorValue(ClassMetadata $metadata) : void
468
    {
469 363
        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...
470 363
            ! $metadata->getReflectionClass() || $metadata->getReflectionClass()->isAbstract()) {
471 363
            return;
472
        }
473
474
        // minor optimization: avoid loading related metadata when not needed
475 4
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
476 4
            if ($discriminatorClass === $metadata->getClassName()) {
477 3
                $metadata->discriminatorValue = $discriminatorValue;
478
479 3
                return;
480
            }
481
        }
482
483
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
484 1
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
485 1
            if ($metadata->getClassName() === $this->getMetadataFor($discriminatorClass)->getClassName()) {
486
                $metadata->discriminatorValue = $discriminatorValue;
487
488
                return;
489
            }
490
        }
491
492 1
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->getClassName(), $metadata->getRootClassName());
493
    }
494
495 367
    private function buildValueGenerationPlan(ClassMetadata $class) : void
496
    {
497 367
        $valueGenerationExecutorList = [];
498
499 367
        foreach ($class->getPropertiesIterator() as $property) {
500 367
            $executor = $property->getValueGenerationExecutor($this->getTargetPlatform());
501
502 367
            if ($executor instanceof ValueGenerationExecutor) {
503 308
                $valueGenerationExecutorList[$property->getName()] = $executor;
504
            }
505
        }
506
507 367
        switch (count($valueGenerationExecutorList)) {
508 367
            case 0:
509 90
                $valueGenerationPlan = new NoopValueGenerationPlan();
510 90
                break;
511
512 308
            case 1:
513 303
                $valueGenerationPlan = new SingleValueGenerationPlan($class, reset($valueGenerationExecutorList));
514 303
                break;
515
516
            default:
517 18
                $valueGenerationPlan = new CompositeValueGenerationPlan($class, $valueGenerationExecutorList);
518 18
                break;
519
        }
520
521 367
        $class->setValueGenerationPlan($valueGenerationPlan);
522 367
    }
523
}
524