Completed
Pull Request — master (#1757)
by Maciej
10:01
created

ClassMetadataFactory::newClassMetadataInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

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