Completed
Pull Request — master (#1787)
by Stefano
21:31
created

ClassMetadataFactory::initializeReflection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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