Completed
Pull Request — master (#1875)
by Andreas
18:19
created

ClassMetadataFactory::getMetadataFor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 1697
    public function setDocumentManager(DocumentManager $dm) : void
50
    {
51 1697
        $this->dm = $dm;
52 1697
    }
53
54 1697
    public function setConfiguration(Configuration $config) : void
55
    {
56 1697
        $this->config = $config;
57 1697
    }
58
59 1433
    public function getMetadataFor($className)
60
    {
61 1433
        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 1432
    protected function initialize() : void
69
    {
70 1432
        $this->driver      = $this->config->getMetadataDriverImpl();
71 1432
        $this->evm         = $this->dm->getEventManager();
72 1432
        $this->initialized = true;
73 1432
    }
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 917
    protected function getDriver()
87
    {
88 917
        return $this->driver;
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94 1428
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
95
    {
96 1428
    }
97
98
    /**
99
     * {@inheritDoc}
100
     */
101 1432
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
102
    {
103 1432
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 1428
    protected function isEntity(ClassMetadataInterface $class) : bool
109
    {
110 1428
        return ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isQueryResultDocument;
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 1432
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents = []) : void
117
    {
118
        /** @var $class ClassMetadata */
119
        /** @var $parent ClassMetadata */
120 1432
        if ($parent) {
121 915
            $class->setInheritanceType($parent->inheritanceType);
122 915
            $class->setDiscriminatorField($parent->discriminatorField);
123 915
            $class->setDiscriminatorMap($parent->discriminatorMap);
124 915
            $class->setDefaultDiscriminatorValue($parent->defaultDiscriminatorValue);
125 915
            $class->setIdGeneratorType($parent->generatorType);
126 915
            $this->addInheritedFields($class, $parent);
127 915
            $this->addInheritedRelations($class, $parent);
128 915
            $this->addInheritedIndexes($class, $parent);
129 915
            $this->setInheritedShardKey($class, $parent);
130 915
            $class->setIdentifier($parent->identifier);
131 915
            $class->setVersioned($parent->isVersioned);
132 915
            $class->setVersionField($parent->versionField);
133 915
            $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
134 915
            $class->setAlsoLoadMethods($parent->alsoLoadMethods);
135 915
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
136 915
            $class->setReadPreference($parent->readPreference, $parent->readPreferenceTags);
137 915
            $class->setWriteConcern($parent->writeConcern);
138 915
            if ($parent->isMappedSuperclass) {
139 852
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
140
            }
141
        }
142
143
        // Invoke driver
144
        try {
145 1432
            $this->driver->loadMetadataForClass($class->getName(), $class);
146 6
        } catch (ReflectionException $e) {
147
            throw MappingException::reflectionFailure($class->getName(), $e);
148
        }
149
150 1428
        $this->validateIdentifier($class);
151
152 1428
        if ($parent && $rootEntityFound && $parent->generatorType === $class->generatorType) {
153 120
            if ($parent->generatorType) {
154 120
                $class->setIdGeneratorType($parent->generatorType);
155
            }
156 120
            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...
157
                $class->setIdGeneratorOptions($parent->generatorOptions);
158
            }
159 120
            if ($parent->idGenerator) {
160 120
                $class->setIdGenerator($parent->idGenerator);
161
            }
162
        } else {
163 1428
            $this->completeIdGeneratorMapping($class);
164
        }
165
166 1428
        if ($parent && $parent->isInheritanceTypeSingleCollection()) {
167 107
            $class->setDatabase($parent->getDatabase());
168 107
            $class->setCollection($parent->getCollection());
169
        }
170
171 1428
        $class->setParentClasses($nonSuperclassParents);
172
173 1428
        if (! $this->evm->hasListeners(Events::loadClassMetadata)) {
174 1426
            return;
175
        }
176
177 2
        $eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
178 2
        $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
179 2
    }
180
181
    /**
182
     * Validates the identifier mapping.
183
     *
184
     * @throws MappingException
185
     */
186 1428
    protected function validateIdentifier(ClassMetadata $class) : void
187
    {
188 1428
        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...
189
            throw MappingException::identifierRequired($class->name);
190
        }
191 1428
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196 1432
    protected function newClassMetadataInstance($className) : ClassMetadata
197
    {
198 1432
        return new ClassMetadata($className);
199
    }
200
201 1428
    private function completeIdGeneratorMapping(ClassMetadata $class) : void
202
    {
203 1428
        $idGenOptions = $class->generatorOptions;
204 1428
        switch ($class->generatorType) {
205
            case ClassMetadata::GENERATOR_TYPE_AUTO:
206 1360
                $class->setIdGenerator(new AutoGenerator());
207 1360
                break;
208 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...
209 9
                $incrementGenerator = new IncrementGenerator();
210 9
                if (isset($idGenOptions['key'])) {
211
                    $incrementGenerator->setKey((string) $idGenOptions['key']);
212
                }
213 9
                if (isset($idGenOptions['collection'])) {
214
                    $incrementGenerator->setCollection((string) $idGenOptions['collection']);
215
                }
216 9
                if (isset($idGenOptions['startingId'])) {
217 1
                    $incrementGenerator->setStartingId((int) $idGenOptions['startingId']);
218
                }
219 9
                $class->setIdGenerator($incrementGenerator);
220 9
                break;
221
            case ClassMetadata::GENERATOR_TYPE_UUID:
222 4
                $uuidGenerator = new UuidGenerator();
223 4
                isset($idGenOptions['salt']) && $uuidGenerator->setSalt((string) $idGenOptions['salt']);
224 4
                $class->setIdGenerator($uuidGenerator);
225 4
                break;
226 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...
227 1
                $alnumGenerator = new AlnumGenerator();
228 1
                if (isset($idGenOptions['pad'])) {
229
                    $alnumGenerator->setPad((int) $idGenOptions['pad']);
230
                }
231 1
                if (isset($idGenOptions['chars'])) {
232 1
                    $alnumGenerator->setChars((string) $idGenOptions['chars']);
233
                } elseif (isset($idGenOptions['awkwardSafe'])) {
234
                    $alnumGenerator->setAwkwardSafeMode((bool) $idGenOptions['awkwardSafe']);
235
                }
236
237 1
                $class->setIdGenerator($alnumGenerator);
238 1
                break;
239
            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
240
                if (empty($idGenOptions['class'])) {
241
                    throw MappingException::missingIdGeneratorClass($class->name);
242
                }
243
244
                $customGenerator = new $idGenOptions['class']();
245
                unset($idGenOptions['class']);
246
                if (! $customGenerator instanceof AbstractIdGenerator) {
247
                    throw MappingException::classIsNotAValidGenerator(get_class($customGenerator));
248
                }
249
250
                $methods = get_class_methods($customGenerator);
251
                foreach ($idGenOptions as $name => $value) {
252
                    $method = 'set' . ucfirst($name);
253
                    if (! in_array($method, $methods)) {
254
                        throw MappingException::missingGeneratorSetter(get_class($customGenerator), $name);
255
                    }
256
257
                    $customGenerator->$method($value);
258
                }
259
                $class->setIdGenerator($customGenerator);
260
                break;
261
            case ClassMetadata::GENERATOR_TYPE_NONE:
262 150
                break;
263
            default:
264
                throw new MappingException('Unknown generator type: ' . $class->generatorType);
265
        }
266 1428
    }
267
268
    /**
269
     * Adds inherited fields to the subclass mapping.
270
     */
271 915
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) : void
272
    {
273 915
        foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
274 138 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...
275 132
                $mapping['inherited'] = $parentClass->name;
276
            }
277 138
            if (! isset($mapping['declared'])) {
278 138
                $mapping['declared'] = $parentClass->name;
279
            }
280 138
            $subClass->addInheritedFieldMapping($mapping);
281
        }
282 915
        foreach ($parentClass->reflFields as $name => $field) {
283 138
            $subClass->reflFields[$name] = $field;
284
        }
285 915
    }
286
287
288
    /**
289
     * Adds inherited association mappings to the subclass mapping.
290
     *
291
     * @throws MappingException
292
     */
293 915
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) : void
294
    {
295 915
        foreach ($parentClass->associationMappings as $field => $mapping) {
296 89
            if ($parentClass->isMappedSuperclass) {
297 4
                $mapping['sourceDocument'] = $subClass->name;
298
            }
299
300 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...
301 85
                $mapping['inherited'] = $parentClass->name;
302
            }
303 89
            if (! isset($mapping['declared'])) {
304 89
                $mapping['declared'] = $parentClass->name;
305
            }
306 89
            $subClass->addInheritedAssociationMapping($mapping);
307
        }
308 915
    }
309
310
    /**
311
     * Adds inherited indexes to the subclass mapping.
312
     */
313 915
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass) : void
314
    {
315 915
        foreach ($parentClass->indexes as $index) {
316 65
            $subClass->addIndex($index['keys'], $index['options']);
317
        }
318 915
    }
319
320
    /**
321
     * Adds inherited shard key to the subclass mapping.
322
     */
323 915
    private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass) : void
324
    {
325 915
        if (! $parentClass->isSharded()) {
326 910
            return;
327
        }
328
329 5
        $subClass->setShardKey(
330 5
            $parentClass->shardKey['keys'],
331 5
            $parentClass->shardKey['options']
332
        );
333 5
    }
334
}
335