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