Completed
Pull Request — master (#2166)
by Andreas
18:54
created

ClassMetadataFactory::onNotFoundMetadata()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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