Completed
Pull Request — master (#1803)
by Maciej
20:22
created

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