Completed
Push — master ( 09b86b...ba0798 )
by Andreas
20:05 queued 12s
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 const E_USER_DEPRECATED;
25
use function assert;
26
use function get_class;
27
use function get_class_methods;
28
use function in_array;
29
use function sprintf;
30
use function trigger_error;
31
use function ucfirst;
32
33
/**
34
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
35
 * metadata mapping informations of a class which describes how a class should be mapped
36
 * to a document database.
37
 *
38
 * @internal
39
 *
40
 * @final
41
 */
42
class ClassMetadataFactory extends AbstractClassMetadataFactory
43
{
44
    /** @var string */
45
    protected $cacheSalt = '$MONGODBODMCLASSMETADATA';
46
47
    /** @var DocumentManager The DocumentManager instance */
48
    private $dm;
49
50
    /** @var Configuration The Configuration instance */
51
    private $config;
52
53
    /** @var MappingDriver The used metadata driver. */
54
    private $driver;
55
56
    /** @var EventManager The event manager instance */
57
    private $evm;
58
59 1762 View Code Duplication
    public function __construct()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
60
    {
61 1762
        if (self::class === static::class) {
62 1762
            return;
63
        }
64
65 1
        @trigger_error(sprintf('The class "%s" extends "%s" which will be final in MongoDB ODM 2.0.', static::class, self::class), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

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