Completed
Pull Request — master (#1875)
by Andreas
16:37
created

ClassMetadataFactory::getMetadataFor()   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 Doctrine\ODM\MongoDB\Proxy\ClassNameResolver;
22
use ReflectionException;
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 1697
    public function setDocumentManager(DocumentManager $dm) : void
51
    {
52 1697
        $this->dm = $dm;
53 1697
    }
54
55 1697
    public function setConfiguration(Configuration $config) : void
56
    {
57 1697
        $this->config = $config;
58 1697
    }
59
60 1433
    public function getMetadataFor($className)
61
    {
62 1433
        return parent::getMetadataFor(ClassNameResolver::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 1432
    protected function initialize() : void
70
    {
71 1432
        $this->driver      = $this->config->getMetadataDriverImpl();
72 1432
        $this->evm         = $this->dm->getEventManager();
73 1432
        $this->initialized = true;
74 1432
    }
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 917
    protected function getDriver()
88
    {
89 917
        return $this->driver;
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95 1428
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
96
    {
97 1428
    }
98
99
    /**
100
     * {@inheritDoc}
101
     */
102 1432
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) : void
103
    {
104 1432
    }
105
106
    /**
107
     * {@inheritDoc}
108
     */
109 1428
    protected function isEntity(ClassMetadataInterface $class) : bool
110
    {
111 1428
        return ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isQueryResultDocument;
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     */
117 1432
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents = []) : void
118
    {
119
        /** @var $class ClassMetadata */
120
        /** @var $parent ClassMetadata */
121 1432
        if ($parent) {
122 915
            $class->setInheritanceType($parent->inheritanceType);
123 915
            $class->setDiscriminatorField($parent->discriminatorField);
124 915
            $class->setDiscriminatorMap($parent->discriminatorMap);
125 915
            $class->setDefaultDiscriminatorValue($parent->defaultDiscriminatorValue);
126 915
            $class->setIdGeneratorType($parent->generatorType);
127 915
            $this->addInheritedFields($class, $parent);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
128 915
            $this->addInheritedRelations($class, $parent);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
129 915
            $this->addInheritedIndexes($class, $parent);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
130 915
            $this->setInheritedShardKey($class, $parent);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
131 915
            $class->setIdentifier($parent->identifier);
132 915
            $class->setVersioned($parent->isVersioned);
133 915
            $class->setVersionField($parent->versionField);
134 915
            $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
135 915
            $class->setAlsoLoadMethods($parent->alsoLoadMethods);
136 915
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
137 915
            $class->setReadPreference($parent->readPreference, $parent->readPreferenceTags);
138 915
            $class->setWriteConcern($parent->writeConcern);
139 915
            if ($parent->isMappedSuperclass) {
140 852
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
141
            }
142
        }
143
144
        // Invoke driver
145
        try {
146 1432
            $this->driver->loadMetadataForClass($class->getName(), $class);
147 6
        } catch (ReflectionException $e) {
148
            throw MappingException::reflectionFailure($class->getName(), $e);
149
        }
150
151 1428
        $this->validateIdentifier($class);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
152
153 1428
        if ($parent && $rootEntityFound && $parent->generatorType === $class->generatorType) {
0 ignored issues
show
Bug introduced by
Accessing generatorType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
154 120
            if ($parent->generatorType) {
155 120
                $class->setIdGeneratorType($parent->generatorType);
156
            }
157 120
            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...
158
                $class->setIdGeneratorOptions($parent->generatorOptions);
159
            }
160 120
            if ($parent->idGenerator) {
161 120
                $class->setIdGenerator($parent->idGenerator);
162
            }
163
        } else {
164 1428
            $this->completeIdGeneratorMapping($class);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
165
        }
166
167 1428
        if ($parent && $parent->isInheritanceTypeSingleCollection()) {
168 107
            $class->setDatabase($parent->getDatabase());
169 107
            $class->setCollection($parent->getCollection());
170
        }
171
172 1428
        $class->setParentClasses($nonSuperclassParents);
173
174 1428
        if (! $this->evm->hasListeners(Events::loadClassMetadata)) {
175 1426
            return;
176
        }
177
178 2
        $eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
179 2
        $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
180 2
    }
181
182
    /**
183
     * Validates the identifier mapping.
184
     *
185
     * @throws MappingException
186
     */
187 1428
    protected function validateIdentifier(ClassMetadata $class) : void
188
    {
189 1428
        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...
190
            throw MappingException::identifierRequired($class->name);
191
        }
192 1428
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 1432
    protected function newClassMetadataInstance($className) : ClassMetadata
198
    {
199 1432
        return new ClassMetadata($className);
200
    }
201
202 1428
    private function completeIdGeneratorMapping(ClassMetadata $class) : void
203
    {
204 1428
        $idGenOptions = $class->generatorOptions;
205 1428
        switch ($class->generatorType) {
206
            case ClassMetadata::GENERATOR_TYPE_AUTO:
207 1360
                $class->setIdGenerator(new AutoGenerator());
208 1360
                break;
209 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...
210 9
                $incrementGenerator = new IncrementGenerator();
211 9
                if (isset($idGenOptions['key'])) {
212
                    $incrementGenerator->setKey((string) $idGenOptions['key']);
213
                }
214 9
                if (isset($idGenOptions['collection'])) {
215
                    $incrementGenerator->setCollection((string) $idGenOptions['collection']);
216
                }
217 9
                if (isset($idGenOptions['startingId'])) {
218 1
                    $incrementGenerator->setStartingId((int) $idGenOptions['startingId']);
219
                }
220 9
                $class->setIdGenerator($incrementGenerator);
221 9
                break;
222
            case ClassMetadata::GENERATOR_TYPE_UUID:
223 4
                $uuidGenerator = new UuidGenerator();
224 4
                isset($idGenOptions['salt']) && $uuidGenerator->setSalt((string) $idGenOptions['salt']);
225 4
                $class->setIdGenerator($uuidGenerator);
226 4
                break;
227 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...
228 1
                $alnumGenerator = new AlnumGenerator();
229 1
                if (isset($idGenOptions['pad'])) {
230
                    $alnumGenerator->setPad((int) $idGenOptions['pad']);
231
                }
232 1
                if (isset($idGenOptions['chars'])) {
233 1
                    $alnumGenerator->setChars((string) $idGenOptions['chars']);
234
                } elseif (isset($idGenOptions['awkwardSafe'])) {
235
                    $alnumGenerator->setAwkwardSafeMode((bool) $idGenOptions['awkwardSafe']);
236
                }
237
238 1
                $class->setIdGenerator($alnumGenerator);
239 1
                break;
240
            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
241
                if (empty($idGenOptions['class'])) {
242
                    throw MappingException::missingIdGeneratorClass($class->name);
243
                }
244
245
                $customGenerator = new $idGenOptions['class']();
246
                unset($idGenOptions['class']);
247
                if (! $customGenerator instanceof AbstractIdGenerator) {
248
                    throw MappingException::classIsNotAValidGenerator(get_class($customGenerator));
249
                }
250
251
                $methods = get_class_methods($customGenerator);
252
                foreach ($idGenOptions as $name => $value) {
253
                    $method = 'set' . ucfirst($name);
254
                    if (! in_array($method, $methods)) {
255
                        throw MappingException::missingGeneratorSetter(get_class($customGenerator), $name);
256
                    }
257
258
                    $customGenerator->$method($value);
259
                }
260
                $class->setIdGenerator($customGenerator);
261
                break;
262
            case ClassMetadata::GENERATOR_TYPE_NONE:
263 150
                break;
264
            default:
265
                throw new MappingException('Unknown generator type: ' . $class->generatorType);
266
        }
267 1428
    }
268
269
    /**
270
     * Adds inherited fields to the subclass mapping.
271
     */
272 915
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) : void
273
    {
274 915
        foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
275 138 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...
276 132
                $mapping['inherited'] = $parentClass->name;
277
            }
278 138
            if (! isset($mapping['declared'])) {
279 138
                $mapping['declared'] = $parentClass->name;
280
            }
281 138
            $subClass->addInheritedFieldMapping($mapping);
282
        }
283 915
        foreach ($parentClass->reflFields as $name => $field) {
284 138
            $subClass->reflFields[$name] = $field;
285
        }
286 915
    }
287
288
289
    /**
290
     * Adds inherited association mappings to the subclass mapping.
291
     *
292
     * @throws MappingException
293
     */
294 915
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) : void
295
    {
296 915
        foreach ($parentClass->associationMappings as $field => $mapping) {
297 89
            if ($parentClass->isMappedSuperclass) {
298 4
                $mapping['sourceDocument'] = $subClass->name;
299
            }
300
301 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...
302 85
                $mapping['inherited'] = $parentClass->name;
303
            }
304 89
            if (! isset($mapping['declared'])) {
305 89
                $mapping['declared'] = $parentClass->name;
306
            }
307 89
            $subClass->addInheritedAssociationMapping($mapping);
308
        }
309 915
    }
310
311
    /**
312
     * Adds inherited indexes to the subclass mapping.
313
     */
314 915
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass) : void
315
    {
316 915
        foreach ($parentClass->indexes as $index) {
317 65
            $subClass->addIndex($index['keys'], $index['options']);
318
        }
319 915
    }
320
321
    /**
322
     * Adds inherited shard key to the subclass mapping.
323
     */
324 915
    private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass) : void
325
    {
326 915
        if (! $parentClass->isSharded()) {
327 910
            return;
328
        }
329
330 5
        $subClass->setShardKey(
331 5
            $parentClass->shardKey['keys'],
332 5
            $parentClass->shardKey['options']
0 ignored issues
show
Documentation introduced by
$parentClass->shardKey['options'] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
333
        );
334 5
    }
335
}
336