Completed
Push — master ( e74e5e...a7cccd )
by Maciej
12s
created

ClassMetadataFactory   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 313
Duplicated Lines 10.22 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 83.44%

Importance

Changes 0
Metric Value
wmc 66
lcom 1
cbo 10
dl 32
loc 313
ccs 126
cts 151
cp 0.8344
rs 3.12
c 0
b 0
f 0

17 Methods

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

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\Common\Persistence\Mapping\AbstractClassMetadataFactory;
9
use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
10
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
11
use Doctrine\Common\Persistence\Mapping\ReflectionService;
12
use Doctrine\ODM\MongoDB\Configuration;
13
use Doctrine\ODM\MongoDB\DocumentManager;
14
use Doctrine\ODM\MongoDB\Event\LoadClassMetadataEventArgs;
15
use Doctrine\ODM\MongoDB\Events;
16
use Doctrine\ODM\MongoDB\Id\AbstractIdGenerator;
17
use Doctrine\ODM\MongoDB\Id\AlnumGenerator;
18
use Doctrine\ODM\MongoDB\Id\AutoGenerator;
19
use Doctrine\ODM\MongoDB\Id\IncrementGenerator;
20
use Doctrine\ODM\MongoDB\Id\UuidGenerator;
21
use ReflectionException;
22
use function get_class;
23
use function get_class_methods;
24
use function in_array;
25
use function ucfirst;
26
27
/**
28
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
29
 * metadata mapping informations of a class which describes how a class should be mapped
30
 * to a document database.
31
 */
32
class ClassMetadataFactory extends AbstractClassMetadataFactory
33
{
34
    /** @var string */
35
    protected $cacheSalt = '$MONGODBODMCLASSMETADATA';
36
37
    /** @var DocumentManager The DocumentManager instance */
38
    private $dm;
39
40
    /** @var Configuration The Configuration instance */
41
    private $config;
42
43
    /** @var MappingDriver The used metadata driver. */
44
    private $driver;
45
46
    /** @var EventManager The event manager instance */
47
    private $evm;
48
49 1698
    public function setDocumentManager(DocumentManager $dm) : void
50
    {
51 1698
        $this->dm = $dm;
52 1698
    }
53
54 1698
    public function setConfiguration(Configuration $config) : void
55
    {
56 1698
        $this->config = $config;
57 1698
    }
58
59 1434
    public function getMetadataFor($className)
60
    {
61 1434
        return parent::getMetadataFor($this->dm->getClassNameResolver()->getRealClass($className));
62
    }
63
64
    /**
65
     * Lazy initialization of this stuff, especially the metadata driver,
66
     * since these are not needed at all when a metadata cache is active.
67
     */
68 1433
    protected function initialize() : void
69
    {
70 1433
        $this->driver      = $this->config->getMetadataDriverImpl();
71 1433
        $this->evm         = $this->dm->getEventManager();
72 1433
        $this->initialized = true;
73 1433
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) : string
79
    {
80
        return $this->config->getDocumentNamespace($namespaceAlias) . '\\' . $simpleClassName;
81
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86 918
    protected function getDriver()
87
    {
88 918
        return $this->driver;
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94 1429
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
95
    {
96 1429
    }
97
98
    /**
99
     * {@inheritDoc}
100
     */
101 1433
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
102
    {
103 1433
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 1429
    protected function isEntity(ClassMetadataInterface $class) : bool
109
    {
110 1429
        return ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isQueryResultDocument;
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 1433
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents = []) : void
117
    {
118
        /** @var $class ClassMetadata */
119
        /** @var $parent ClassMetadata */
120 1433
        if ($parent) {
121 916
            $class->setInheritanceType($parent->inheritanceType);
122 916
            $class->setDiscriminatorField($parent->discriminatorField);
123 916
            $class->setDiscriminatorMap($parent->discriminatorMap);
124 916
            $class->setDefaultDiscriminatorValue($parent->defaultDiscriminatorValue);
125 916
            $class->setIdGeneratorType($parent->generatorType);
126 916
            $this->addInheritedFields($class, $parent);
127 916
            $this->addInheritedRelations($class, $parent);
128 916
            $this->addInheritedIndexes($class, $parent);
129 916
            $this->setInheritedShardKey($class, $parent);
130 916
            $class->setIdentifier($parent->identifier);
131 916
            $class->setVersioned($parent->isVersioned);
132 916
            $class->setVersionField($parent->versionField);
133 916
            $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
134 916
            $class->setAlsoLoadMethods($parent->alsoLoadMethods);
135 916
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
136 916
            $class->setReadPreference($parent->readPreference, $parent->readPreferenceTags);
137 916
            $class->setWriteConcern($parent->writeConcern);
138
139 916
            if ($parent->isMappedSuperclass) {
140 852
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
141
            }
142
143 916
            if ($parent->isFile) {
144 1
                $class->isFile = true;
145 1
                $class->setBucketName($parent->bucketName);
146
147 1
                if ($parent->chunkSizeBytes !== null) {
148 1
                    $class->setChunkSizeBytes($parent->chunkSizeBytes);
149
                }
150
            }
151
        }
152
153
        // Invoke driver
154
        try {
155 1433
            $this->driver->loadMetadataForClass($class->getName(), $class);
156 6
        } catch (ReflectionException $e) {
157
            throw MappingException::reflectionFailure($class->getName(), $e);
158
        }
159
160 1429
        $this->validateIdentifier($class);
161
162 1429
        if ($parent && $rootEntityFound && $parent->generatorType === $class->generatorType) {
163 121
            if ($parent->generatorType) {
164 121
                $class->setIdGeneratorType($parent->generatorType);
165
            }
166 121
            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...
167
                $class->setIdGeneratorOptions($parent->generatorOptions);
168
            }
169 121
            if ($parent->idGenerator) {
170 121
                $class->setIdGenerator($parent->idGenerator);
171
            }
172
        } else {
173 1429
            $this->completeIdGeneratorMapping($class);
174
        }
175
176 1429
        if ($parent && $parent->isInheritanceTypeSingleCollection()) {
177 107
            $class->setDatabase($parent->getDatabase());
178 107
            $class->setCollection($parent->getCollection());
179
        }
180
181 1429
        $class->setParentClasses($nonSuperclassParents);
182
183 1429
        if (! $this->evm->hasListeners(Events::loadClassMetadata)) {
184 1427
            return;
185
        }
186
187 2
        $eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
188 2
        $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
189 2
    }
190
191
    /**
192
     * Validates the identifier mapping.
193
     *
194
     * @throws MappingException
195
     */
196 1429
    protected function validateIdentifier(ClassMetadata $class) : void
197
    {
198 1429
        if (! $class->identifier && ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isQueryResultDocument) {
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...
199
            throw MappingException::identifierRequired($class->name);
200
        }
201 1429
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206 1433
    protected function newClassMetadataInstance($className) : ClassMetadata
207
    {
208 1433
        return new ClassMetadata($className);
209
    }
210
211 1429
    private function completeIdGeneratorMapping(ClassMetadata $class) : void
212
    {
213 1429
        $idGenOptions = $class->generatorOptions;
214 1429
        switch ($class->generatorType) {
215
            case ClassMetadata::GENERATOR_TYPE_AUTO:
216 1361
                $class->setIdGenerator(new AutoGenerator());
217 1361
                break;
218 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...
219 9
                $incrementGenerator = new IncrementGenerator();
220 9
                if (isset($idGenOptions['key'])) {
221
                    $incrementGenerator->setKey((string) $idGenOptions['key']);
222
                }
223 9
                if (isset($idGenOptions['collection'])) {
224
                    $incrementGenerator->setCollection((string) $idGenOptions['collection']);
225
                }
226 9
                if (isset($idGenOptions['startingId'])) {
227 1
                    $incrementGenerator->setStartingId((int) $idGenOptions['startingId']);
228
                }
229 9
                $class->setIdGenerator($incrementGenerator);
230 9
                break;
231
            case ClassMetadata::GENERATOR_TYPE_UUID:
232 4
                $uuidGenerator = new UuidGenerator();
233 4
                isset($idGenOptions['salt']) && $uuidGenerator->setSalt((string) $idGenOptions['salt']);
234 4
                $class->setIdGenerator($uuidGenerator);
235 4
                break;
236 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...
237 1
                $alnumGenerator = new AlnumGenerator();
238 1
                if (isset($idGenOptions['pad'])) {
239
                    $alnumGenerator->setPad((int) $idGenOptions['pad']);
240
                }
241 1
                if (isset($idGenOptions['chars'])) {
242 1
                    $alnumGenerator->setChars((string) $idGenOptions['chars']);
243
                } elseif (isset($idGenOptions['awkwardSafe'])) {
244
                    $alnumGenerator->setAwkwardSafeMode((bool) $idGenOptions['awkwardSafe']);
245
                }
246
247 1
                $class->setIdGenerator($alnumGenerator);
248 1
                break;
249
            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
250
                if (empty($idGenOptions['class'])) {
251
                    throw MappingException::missingIdGeneratorClass($class->name);
252
                }
253
254
                $customGenerator = new $idGenOptions['class']();
255
                unset($idGenOptions['class']);
256
                if (! $customGenerator instanceof AbstractIdGenerator) {
257
                    throw MappingException::classIsNotAValidGenerator(get_class($customGenerator));
258
                }
259
260
                $methods = get_class_methods($customGenerator);
261
                foreach ($idGenOptions as $name => $value) {
262
                    $method = 'set' . ucfirst($name);
263
                    if (! in_array($method, $methods)) {
264
                        throw MappingException::missingGeneratorSetter(get_class($customGenerator), $name);
265
                    }
266
267
                    $customGenerator->$method($value);
268
                }
269
                $class->setIdGenerator($customGenerator);
270
                break;
271
            case ClassMetadata::GENERATOR_TYPE_NONE:
272 150
                break;
273
            default:
274
                throw new MappingException('Unknown generator type: ' . $class->generatorType);
275
        }
276 1429
    }
277
278
    /**
279
     * Adds inherited fields to the subclass mapping.
280
     */
281 916
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) : void
282
    {
283 916
        foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
284 139 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...
285 133
                $mapping['inherited'] = $parentClass->name;
286
            }
287 139
            if (! isset($mapping['declared'])) {
288 139
                $mapping['declared'] = $parentClass->name;
289
            }
290 139
            $subClass->addInheritedFieldMapping($mapping);
291
        }
292 916
        foreach ($parentClass->reflFields as $name => $field) {
293 139
            $subClass->reflFields[$name] = $field;
294
        }
295 916
    }
296
297
298
    /**
299
     * Adds inherited association mappings to the subclass mapping.
300
     *
301
     * @throws MappingException
302
     */
303 916
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) : void
304
    {
305 916
        foreach ($parentClass->associationMappings as $field => $mapping) {
306 89
            if ($parentClass->isMappedSuperclass) {
307 4
                $mapping['sourceDocument'] = $subClass->name;
308
            }
309
310 89 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...
311 85
                $mapping['inherited'] = $parentClass->name;
312
            }
313 89
            if (! isset($mapping['declared'])) {
314 89
                $mapping['declared'] = $parentClass->name;
315
            }
316 89
            $subClass->addInheritedAssociationMapping($mapping);
317
        }
318 916
    }
319
320
    /**
321
     * Adds inherited indexes to the subclass mapping.
322
     */
323 916
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass) : void
324
    {
325 916
        foreach ($parentClass->indexes as $index) {
326 65
            $subClass->addIndex($index['keys'], $index['options']);
327
        }
328 916
    }
329
330
    /**
331
     * Adds inherited shard key to the subclass mapping.
332
     */
333 916
    private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass) : void
334
    {
335 916
        if (! $parentClass->isSharded()) {
336 911
            return;
337
        }
338
339 5
        $subClass->setShardKey(
340 5
            $parentClass->shardKey['keys'],
341 5
            $parentClass->shardKey['options']
342
        );
343 5
    }
344
}
345