ClassMetadataFactory   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 336
Duplicated Lines 9.52 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 84.05%

Importance

Changes 0
Metric Value
wmc 68
lcom 1
cbo 11
dl 32
loc 336
ccs 137
cts 163
cp 0.8405
rs 2.96
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setDocumentManager() 0 4 1
A setConfiguration() 0 4 1
A getMetadataFor() 0 4 1
A initialize() 0 11 2
A onNotFoundMetadata() 0 12 2
A getFqcnFromAlias() 0 4 1
A getDriver() 0 4 1
A wakeupReflection() 0 3 1
A initializeReflection() 0 3 1
D doLoadMetadata() 0 73 15
A newClassMetadataInstance() 0 4 1
D completeIdGeneratorMapping() 26 68 18
A addInheritedFields() 3 15 6
A addInheritedRelations() 3 16 6
A addInheritedIndexes() 0 6 2
A setInheritedShardKey() 0 11 2
A isEntity() 0 6 4
A validateIdentifier() 0 6 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ClassMetadataFactory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMetadataFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\ODM\MongoDB\Configuration;
9
use Doctrine\ODM\MongoDB\ConfigurationException;
10
use Doctrine\ODM\MongoDB\DocumentManager;
11
use Doctrine\ODM\MongoDB\Event\LoadClassMetadataEventArgs;
12
use Doctrine\ODM\MongoDB\Event\OnClassMetadataNotFoundEventArgs;
13
use Doctrine\ODM\MongoDB\Events;
14
use Doctrine\ODM\MongoDB\Id\AlnumGenerator;
15
use Doctrine\ODM\MongoDB\Id\AutoGenerator;
16
use Doctrine\ODM\MongoDB\Id\IdGenerator;
17
use Doctrine\ODM\MongoDB\Id\IncrementGenerator;
18
use Doctrine\ODM\MongoDB\Id\UuidGenerator;
19
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
20
use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
21
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
22
use Doctrine\Persistence\Mapping\ReflectionService;
23
use ReflectionException;
24
use function assert;
25
use function get_class;
26
use function get_class_methods;
27
use function in_array;
28
use function interface_exists;
29
use function ucfirst;
30
31
/**
32
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
33
 * metadata mapping informations of a class which describes how a class should be mapped
34
 * to a document database.
35
 *
36
 * @internal
37
 */
38
final class ClassMetadataFactory extends AbstractClassMetadataFactory
39
{
40
    /** @var string */
41
    protected $cacheSalt = '$MONGODBODMCLASSMETADATA';
42
43
    /** @var DocumentManager The DocumentManager instance */
44
    private $dm;
45
46
    /** @var Configuration The Configuration instance */
47
    private $config;
48
49
    /** @var MappingDriver The used metadata driver. */
50
    private $driver;
51
52
    /** @var EventManager The event manager instance */
53
    private $evm;
54
55 1832
    public function setDocumentManager(DocumentManager $dm) : void
56
    {
57 1832
        $this->dm = $dm;
58 1832
    }
59
60 1832
    public function setConfiguration(Configuration $config) : void
61
    {
62 1832
        $this->config = $config;
63 1832
    }
64
65 1607
    public function getMetadataFor($className)
66
    {
67 1607
        return parent::getMetadataFor($this->dm->getClassNameResolver()->getRealClass($className));
68
    }
69
70
    /**
71
     * Lazy initialization of this stuff, especially the metadata driver,
72
     * since these are not needed at all when a metadata cache is active.
73
     */
74 1606
    protected function initialize() : void
75
    {
76 1606
        $driver = $this->config->getMetadataDriverImpl();
77 1606
        if ($driver === null) {
78
            throw ConfigurationException::noMetadataDriverConfigured();
79
        }
80
81 1606
        $this->driver      = $driver;
82 1606
        $this->evm         = $this->dm->getEventManager();
83 1606
        $this->initialized = true;
84 1606
    }
85
86
    /**
87
     * {@inheritDoc}
88
     */
89 21
    protected function onNotFoundMetadata($className)
90
    {
91 21
        if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
92 19
            return null;
93
        }
94
95 2
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->dm);
96
97 2
        $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
98
99 2
        return $eventArgs->getFoundMetadata();
100
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) : string
106
    {
107
        return $this->config->getDocumentNamespace($namespaceAlias) . '\\' . $simpleClassName;
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113 1042
    protected function getDriver()
114
    {
115 1042
        return $this->driver;
116
    }
117
118
    /**
119
     * {@inheritDoc}
120
     */
121 1598
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
122
    {
123 1598
    }
124
125
    /**
126
     * {@inheritDoc}
127
     */
128 1606
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
129
    {
130 1606
    }
131
132
    /**
133
     * {@inheritDoc}
134
     */
135 1598
    protected function isEntity(ClassMetadataInterface $class) : bool
136
    {
137 1598
        assert($class instanceof ClassMetadata);
138
139 1598
        return ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isQueryResultDocument && ! $class->isView();
140
    }
141
142
    /**
143
     * {@inheritDoc}
144
     */
145 1606
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents = []) : void
146
    {
147 1606
        assert($class instanceof ClassMetadata);
148 1606
        if ($parent instanceof ClassMetadata) {
149 1037
            $class->setInheritanceType($parent->inheritanceType);
150 1037
            $class->setDiscriminatorField($parent->discriminatorField);
151 1037
            $class->setDiscriminatorMap($parent->discriminatorMap);
152 1037
            $class->setDefaultDiscriminatorValue($parent->defaultDiscriminatorValue);
153 1037
            $class->setIdGeneratorType($parent->generatorType);
154 1037
            $this->addInheritedFields($class, $parent);
155 1037
            $this->addInheritedRelations($class, $parent);
156 1037
            $this->addInheritedIndexes($class, $parent);
157 1037
            $this->setInheritedShardKey($class, $parent);
158 1037
            $class->setIdentifier($parent->identifier);
159 1037
            $class->setVersioned($parent->isVersioned);
160 1037
            $class->setVersionField($parent->versionField);
161 1037
            $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
162 1037
            $class->setAlsoLoadMethods($parent->alsoLoadMethods);
163 1037
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
164 1037
            $class->setReadPreference($parent->readPreference, $parent->readPreferenceTags);
165 1037
            $class->setWriteConcern($parent->writeConcern);
166
167 1037
            if ($parent->isMappedSuperclass) {
168 970
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
169
            }
170
171 1037
            if ($parent->isFile) {
172 1
                $class->isFile = true;
173 1
                $class->setBucketName($parent->bucketName);
174
175 1
                if ($parent->chunkSizeBytes !== null) {
176 1
                    $class->setChunkSizeBytes($parent->chunkSizeBytes);
177
                }
178
            }
179
        }
180
181
        // Invoke driver
182
        try {
183 1606
            $this->driver->loadMetadataForClass($class->getName(), $class);
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
184 13
        } catch (ReflectionException $e) {
185
            throw MappingException::reflectionFailure($class->getName(), $e);
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
186
        }
187
188 1598
        $this->validateIdentifier($class);
189
190 1598
        if ($parent instanceof ClassMetadata && $rootEntityFound && $parent->generatorType === $class->generatorType) {
191 185
            if ($parent->generatorType) {
192 185
                $class->setIdGeneratorType($parent->generatorType);
193
            }
194 185
            if ($parent->generatorOptions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parent->generatorOptions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
195
                $class->setIdGeneratorOptions($parent->generatorOptions);
196
            }
197 185
            if ($parent->idGenerator) {
198 185
                $class->setIdGenerator($parent->idGenerator);
199
            }
200
        } else {
201 1598
            $this->completeIdGeneratorMapping($class);
202
        }
203
204 1598
        if ($parent instanceof ClassMetadata && $parent->isInheritanceTypeSingleCollection()) {
205 174
            $class->setDatabase($parent->getDatabase());
206 174
            $class->setCollection($parent->getCollection());
207
        }
208
209 1598
        $class->setParentClasses($nonSuperclassParents);
210
211 1598
        if (! $this->evm->hasListeners(Events::loadClassMetadata)) {
212 1594
            return;
213
        }
214
215 4
        $eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
216 4
        $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
217 4
    }
218
219
    /**
220
     * Validates the identifier mapping.
221
     *
222
     * @throws MappingException
223
     */
224 1598
    protected function validateIdentifier(ClassMetadata $class) : void
225
    {
226 1598
        if (! $class->identifier && $this->isEntity($class)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->identifier of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
227
            throw MappingException::identifierRequired($class->name);
228
        }
229 1598
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234 1606
    protected function newClassMetadataInstance($className) : ClassMetadata
235
    {
236 1606
        return new ClassMetadata($className);
237
    }
238
239 1598
    private function completeIdGeneratorMapping(ClassMetadata $class) : void
240
    {
241 1598
        $idGenOptions = $class->generatorOptions;
242 1598
        switch ($class->generatorType) {
243
            case ClassMetadata::GENERATOR_TYPE_AUTO:
244 1532
                $class->setIdGenerator(new AutoGenerator());
245 1532
                break;
246 View Code Duplication
            case ClassMetadata::GENERATOR_TYPE_INCREMENT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
247 9
                $incrementGenerator = new IncrementGenerator();
248 9
                if (isset($idGenOptions['key'])) {
249
                    $incrementGenerator->setKey((string) $idGenOptions['key']);
250
                }
251 9
                if (isset($idGenOptions['collection'])) {
252
                    $incrementGenerator->setCollection((string) $idGenOptions['collection']);
253
                }
254 9
                if (isset($idGenOptions['startingId'])) {
255 1
                    $incrementGenerator->setStartingId((int) $idGenOptions['startingId']);
256
                }
257 9
                $class->setIdGenerator($incrementGenerator);
258 9
                break;
259
            case ClassMetadata::GENERATOR_TYPE_UUID:
260 4
                $uuidGenerator = new UuidGenerator();
261 4
                if (isset($idGenOptions['salt'])) {
262 2
                    $uuidGenerator->setSalt((string) $idGenOptions['salt']);
263
                }
264 4
                $class->setIdGenerator($uuidGenerator);
265 4
                break;
266 View Code Duplication
            case ClassMetadata::GENERATOR_TYPE_ALNUM:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267 1
                $alnumGenerator = new AlnumGenerator();
268 1
                if (isset($idGenOptions['pad'])) {
269
                    $alnumGenerator->setPad((int) $idGenOptions['pad']);
270
                }
271 1
                if (isset($idGenOptions['chars'])) {
272 1
                    $alnumGenerator->setChars((string) $idGenOptions['chars']);
273
                } elseif (isset($idGenOptions['awkwardSafe'])) {
274
                    $alnumGenerator->setAwkwardSafeMode((bool) $idGenOptions['awkwardSafe']);
275
                }
276
277 1
                $class->setIdGenerator($alnumGenerator);
278 1
                break;
279
            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
280
                if (empty($idGenOptions['class'])) {
281
                    throw MappingException::missingIdGeneratorClass($class->name);
282
                }
283
284
                $customGenerator = new $idGenOptions['class']();
285
                unset($idGenOptions['class']);
286
                if (! $customGenerator instanceof IdGenerator) {
287
                    throw MappingException::classIsNotAValidGenerator(get_class($customGenerator));
288
                }
289
290
                $methods = get_class_methods($customGenerator);
291
                foreach ($idGenOptions as $name => $value) {
292
                    $method = 'set' . ucfirst($name);
293
                    if (! in_array($method, $methods)) {
294
                        throw MappingException::missingGeneratorSetter(get_class($customGenerator), $name);
295
                    }
296
297
                    $customGenerator->$method($value);
298
                }
299
                $class->setIdGenerator($customGenerator);
300
                break;
301
            case ClassMetadata::GENERATOR_TYPE_NONE:
302 215
                break;
303
            default:
304
                throw new MappingException('Unknown generator type: ' . $class->generatorType);
305
        }
306 1598
    }
307
308
    /**
309
     * Adds inherited fields to the subclass mapping.
310
     */
311 1037
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) : void
312
    {
313 1037
        foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
314 205 View Code Duplication
            if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315 197
                $mapping['inherited'] = $parentClass->name;
316
            }
317 205
            if (! isset($mapping['declared'])) {
318 205
                $mapping['declared'] = $parentClass->name;
319
            }
320 205
            $subClass->addInheritedFieldMapping($mapping);
321
        }
322 1037
        foreach ($parentClass->reflFields as $name => $field) {
323 205
            $subClass->reflFields[$name] = $field;
324
        }
325 1037
    }
326
327
    /**
328
     * Adds inherited association mappings to the subclass mapping.
329
     *
330
     * @throws MappingException
331
     */
332 1037
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) : void
333
    {
334 1037
        foreach ($parentClass->associationMappings as $field => $mapping) {
335 153
            if ($parentClass->isMappedSuperclass) {
336 4
                $mapping['sourceDocument'] = $subClass->name;
337
            }
338
339 153 View Code Duplication
            if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340 149
                $mapping['inherited'] = $parentClass->name;
341
            }
342 153
            if (! isset($mapping['declared'])) {
343 153
                $mapping['declared'] = $parentClass->name;
344
            }
345 153
            $subClass->addInheritedAssociationMapping($mapping);
346
        }
347 1037
    }
348
349
    /**
350
     * Adds inherited indexes to the subclass mapping.
351
     */
352 1037
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass) : void
353
    {
354 1037
        foreach ($parentClass->indexes as $index) {
355 126
            $subClass->addIndex($index['keys'], $index['options']);
356
        }
357 1037
    }
358
359
    /**
360
     * Adds inherited shard key to the subclass mapping.
361
     */
362 1037
    private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass) : void
363
    {
364 1037
        if (! $parentClass->isSharded()) {
365 1032
            return;
366
        }
367
368 5
        $subClass->setShardKey(
369 5
            $parentClass->shardKey['keys'],
370 5
            $parentClass->shardKey['options']
371
        );
372 5
    }
373
}
374
375 1
interface_exists(ClassMetadataInterface::class);
376
interface_exists(ReflectionService::class);
377