Completed
Pull Request — master (#1803)
by Maciej
15:26 queued 06:04
created

ClassMetadataFactory   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 316
Duplicated Lines 10.13 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 82.88%

Importance

Changes 0
Metric Value
wmc 63
lcom 1
cbo 9
dl 32
loc 316
ccs 121
cts 146
cp 0.8288
rs 5.8893
c 0
b 0
f 0

16 Methods

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