Completed
Pull Request — master (#1984)
by Maciej
23:09
created

ClassMetadataFactory   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 336
Duplicated Lines 9.52 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 83.85%

Importance

Changes 0
Metric Value
wmc 69
lcom 1
cbo 11
dl 32
loc 336
ccs 135
cts 161
cp 0.8385
rs 2.88
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
A isEntity() 0 5 3
D doLoadMetadata() 0 73 15
A validateIdentifier() 0 6 5
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

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