Completed
Push — master ( a8fe50...bce26f )
by Maciej
13s
created

ODM/MongoDB/Mapping/ClassMetadataFactory.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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