Completed
Pull Request — master (#1803)
by Andreas
17:38 queued 07:32
created

ClassMetadataFactory::setConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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