Completed
Pull Request — master (#1984)
by Maciej
22:01
created

ClassMetadataFactory::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 8
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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