Completed
Pull Request — master (#1875)
by Andreas
61:52
created

ClassMetadataFactory::wakeupReflection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 1
cts 1
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 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 1699
    public function setDocumentManager(DocumentManager $dm) : void
50
    {
51 1699
        $this->dm = $dm;
52 1699
    }
53
54 1699
    public function setConfiguration(Configuration $config) : void
55
    {
56 1699
        $this->config = $config;
57 1699
    }
58
59
    public function getMetadataFor($className)
60
    {
61
        return parent::getMetadataFor($this->dm->getClassNameResolver()->getRealClass($className));
62
    }
63 1434
64
    /**
65 1434
     * Lazy initialization of this stuff, especially the metadata driver,
66 1434
     * since these are not needed at all when a metadata cache is active.
67 1434
     */
68 1434
    protected function initialize() : void
69
    {
70
        $this->driver      = $this->config->getMetadataDriverImpl();
71
        $this->evm         = $this->dm->getEventManager();
72
        $this->initialized = true;
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) : string
79
    {
80
        return $this->config->getDocumentNamespace($namespaceAlias) . '\\' . $simpleClassName;
81 917
    }
82
83 917
    /**
84
     * {@inheritDoc}
85
     */
86
    protected function getDriver()
87
    {
88
        return $this->driver;
89 1430
    }
90
91 1430
    /**
92
     * {@inheritDoc}
93
     */
94
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
95
    {
96 1434
    }
97
98 1434
    /**
99
     * {@inheritDoc}
100
     */
101
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
102
    {
103 1430
    }
104
105 1430
    /**
106
     * {@inheritDoc}
107
     */
108
    protected function isEntity(ClassMetadataInterface $class) : bool
109
    {
110
        return ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isQueryResultDocument;
111 1434
    }
112
113
    /**
114
     * {@inheritDoc}
115 1434
     */
116 915
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents = []) : void
117 915
    {
118 915
        /** @var $class ClassMetadata */
119 915
        /** @var $parent ClassMetadata */
120 915
        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 852
            $class->setAlsoLoadMethods($parent->alsoLoadMethods);
135
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
136
            $class->setReadPreference($parent->readPreference, $parent->readPreferenceTags);
137
            $class->setWriteConcern($parent->writeConcern);
138
            if ($parent->isMappedSuperclass) {
139
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
140 1434
            }
141 6
        }
142
143
        // Invoke driver
144
        try {
145 1430
            $this->driver->loadMetadataForClass($class->getName(), $class);
146
        } catch (ReflectionException $e) {
147 1430
            throw MappingException::reflectionFailure($class->getName(), $e);
148 120
        }
149 120
150
        $this->validateIdentifier($class);
151 120
152
        if ($parent && $rootEntityFound && $parent->generatorType === $class->generatorType) {
153
            if ($parent->generatorType) {
154 120
                $class->setIdGeneratorType($parent->generatorType);
155 120
            }
156
            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 1430
            }
159
            if ($parent->idGenerator) {
160
                $class->setIdGenerator($parent->idGenerator);
161 1430
            }
162 107
        } else {
163 107
            $this->completeIdGeneratorMapping($class);
164
        }
165
166 1430
        if ($parent && $parent->isInheritanceTypeSingleCollection()) {
167
            $class->setDatabase($parent->getDatabase());
168 1430
            $class->setCollection($parent->getCollection());
169 1428
        }
170
171
        $class->setParentClasses($nonSuperclassParents);
172 2
173 2
        if (! $this->evm->hasListeners(Events::loadClassMetadata)) {
174 2
            return;
175
        }
176
177
        $eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
178
        $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
179
    }
180
181 1430
    /**
182
     * Validates the identifier mapping.
183 1430
     *
184
     * @throws MappingException
185
     */
186 1430
    protected function validateIdentifier(ClassMetadata $class) : void
187
    {
188
        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 1434
    }
192
193 1434
    /**
194
     * {@inheritdoc}
195
     */
196 1430
    protected function newClassMetadataInstance($className) : ClassMetadata
197
    {
198 1430
        return new ClassMetadata($className);
199 1430
    }
200
201 1362
    private function completeIdGeneratorMapping(ClassMetadata $class) : void
202 1362
    {
203
        $idGenOptions = $class->generatorOptions;
204 9
        switch ($class->generatorType) {
205 9
            case ClassMetadata::GENERATOR_TYPE_AUTO:
206
                $class->setIdGenerator(new AutoGenerator());
207
                break;
208 9 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
                $incrementGenerator = new IncrementGenerator();
210
                if (isset($idGenOptions['key'])) {
211 9
                    $incrementGenerator->setKey((string) $idGenOptions['key']);
212 1
                }
213
                if (isset($idGenOptions['collection'])) {
214 9
                    $incrementGenerator->setCollection((string) $idGenOptions['collection']);
215 9
                }
216
                if (isset($idGenOptions['startingId'])) {
217 4
                    $incrementGenerator->setStartingId((int) $idGenOptions['startingId']);
218 4
                }
219 4
                $class->setIdGenerator($incrementGenerator);
220 4
                break;
221
            case ClassMetadata::GENERATOR_TYPE_UUID:
222 1
                $uuidGenerator = new UuidGenerator();
223 1
                isset($idGenOptions['salt']) && $uuidGenerator->setSalt((string) $idGenOptions['salt']);
224
                $class->setIdGenerator($uuidGenerator);
225
                break;
226 1 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
                if (isset($idGenOptions['pad'])) {
229
                    $alnumGenerator->setPad((int) $idGenOptions['pad']);
230
                }
231
                if (isset($idGenOptions['chars'])) {
232 1
                    $alnumGenerator->setChars((string) $idGenOptions['chars']);
233 1
                } elseif (isset($idGenOptions['awkwardSafe'])) {
234
                    $alnumGenerator->setAwkwardSafeMode((bool) $idGenOptions['awkwardSafe']);
235
                }
236
237
                $class->setIdGenerator($alnumGenerator);
238
                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 150
                    $customGenerator->$method($value);
258
                }
259
                $class->setIdGenerator($customGenerator);
260
                break;
261 1430
            case ClassMetadata::GENERATOR_TYPE_NONE:
262
                break;
263
            default:
264
                throw new MappingException('Unknown generator type: ' . $class->generatorType);
265
        }
266 915
    }
267
268 915
    /**
269 138
     * Adds inherited fields to the subclass mapping.
270 132
     */
271
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) : void
272 138
    {
273 138
        foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
274 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 138
                $mapping['inherited'] = $parentClass->name;
276
            }
277 915
            if (! isset($mapping['declared'])) {
278 138
                $mapping['declared'] = $parentClass->name;
279
            }
280 915
            $subClass->addInheritedFieldMapping($mapping);
281
        }
282
        foreach ($parentClass->reflFields as $name => $field) {
283
            $subClass->reflFields[$name] = $field;
284
        }
285
    }
286
287
288 915
    /**
289
     * Adds inherited association mappings to the subclass mapping.
290 915
     *
291 89
     * @throws MappingException
292 4
     */
293
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) : void
294
    {
295 89
        foreach ($parentClass->associationMappings as $field => $mapping) {
296 85
            if ($parentClass->isMappedSuperclass) {
297
                $mapping['sourceDocument'] = $subClass->name;
298 89
            }
299 89
300 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 89
                $mapping['inherited'] = $parentClass->name;
302
            }
303 915
            if (! isset($mapping['declared'])) {
304
                $mapping['declared'] = $parentClass->name;
305
            }
306
            $subClass->addInheritedAssociationMapping($mapping);
307
        }
308 915
    }
309
310 915
    /**
311 65
     * Adds inherited indexes to the subclass mapping.
312
     */
313 915
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass) : void
314
    {
315
        foreach ($parentClass->indexes as $index) {
316
            $subClass->addIndex($index['keys'], $index['options']);
317
        }
318 915
    }
319
320 915
    /**
321 910
     * Adds inherited shard key to the subclass mapping.
322
     */
323
    private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass) : void
324 5
    {
325 5
        if (! $parentClass->isSharded()) {
326 5
            return;
327
        }
328 5
329
        $subClass->setShardKey(
330
            $parentClass->shardKey['keys'],
331
            $parentClass->shardKey['options']
332
        );
333
    }
334
}
335