Completed
Pull Request — master (#1263)
by Andreas
14:36
created

ClassMetadataFactory::validateIdentifier()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 5.3906

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 3
cts 4
cp 0.75
rs 8.8571
cc 5
eloc 3
nc 2
nop 1
crap 5.3906
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Mapping;
21
22
use Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory;
23
use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
24
use Doctrine\Common\Persistence\Mapping\ReflectionService;
25
use Doctrine\ODM\MongoDB\Configuration;
26
use Doctrine\ODM\MongoDB\DocumentManager;
27
use Doctrine\ODM\MongoDB\Event\LoadClassMetadataEventArgs;
28
use Doctrine\ODM\MongoDB\Events;
29
use Doctrine\ODM\MongoDB\Id\AbstractIdGenerator;
30
use Doctrine\ODM\MongoDB\Id\AlnumGenerator;
31
use Doctrine\ODM\MongoDB\Id\AutoGenerator;
32
use Doctrine\ODM\MongoDB\Id\IncrementGenerator;
33
use Doctrine\ODM\MongoDB\Id\UuidGenerator;
34
35
/**
36
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
37
 * metadata mapping informations of a class which describes how a class should be mapped
38
 * to a document database.
39
 *
40
 * @since       1.0
41
 */
42
class ClassMetadataFactory extends AbstractClassMetadataFactory
43
{
44
    protected $cacheSalt = "\$MONGODBODMCLASSMETADATA";
45
46
    /** @var DocumentManager The DocumentManager instance */
47
    private $dm;
48
49
    /** @var Configuration The Configuration instance */
50
    private $config;
51
52
    /** @var \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver The used metadata driver. */
53
    private $driver;
54
55
    /** @var \Doctrine\Common\EventManager The event manager instance */
56
    private $evm;
57
58
    /**
59
     * Sets the DocumentManager instance for this class.
60
     *
61
     * @param DocumentManager $dm The DocumentManager instance
62
     */
63 964
    public function setDocumentManager(DocumentManager $dm)
64
    {
65 964
        $this->dm = $dm;
66 964
    }
67
68
    /**
69
     * Sets the Configuration instance
70
     *
71
     * @param Configuration $config
72
     */
73 964
    public function setConfiguration(Configuration $config)
74
    {
75 964
        $this->config = $config;
76 964
    }
77
78
    /**
79
     * Lazy initialization of this stuff, especially the metadata driver,
80
     * since these are not needed at all when a metadata cache is active.
81
     */
82 827
    protected function initialize()
83
    {
84 827
        $this->driver = $this->config->getMetadataDriverImpl();
85 827
        $this->evm = $this->dm->getEventManager();
86 827
        $this->initialized = true;
87 827
    }
88
89
    /**
90
     * {@inheritDoc}
91
     */
92 2
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
93
    {
94 2
        return $this->config->getDocumentNamespace($namespaceAlias) . '\\' . $simpleClassName;
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100 332
    protected function getDriver()
101
    {
102 332
        return $this->driver;
103
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 824
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService)
109
    {
110 824
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115 827
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService)
116
    {
117 827
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122 824
    protected function isEntity(ClassMetadataInterface $class)
123
    {
124 824
        return ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isAggregationResultDocument;
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 isAggregationResultDocument 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...
125
    }
126
127
    /**
128
     * {@inheritDoc}
129
     */
130 827
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents = array())
131 2
    {
132
        /** @var $class ClassMetadata */
133
        /** @var $parent ClassMetadata */
134 827
        if ($parent) {
135 331
            $class->setInheritanceType($parent->inheritanceType);
136 331
            $class->setDiscriminatorField($parent->discriminatorField);
137 331
            $class->setDiscriminatorMap($parent->discriminatorMap);
138 331
            $class->setDefaultDiscriminatorValue($parent->defaultDiscriminatorValue);
139 331
            $class->setIdGeneratorType($parent->generatorType);
140 331
            $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...
141 331
            $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...
142 331
            $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...
143 331
            $class->setIdentifier($parent->identifier);
144 331
            $class->setVersioned($parent->isVersioned);
145 331
            $class->setVersionField($parent->versionField);
146 331
            $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
147 331
            $class->setAlsoLoadMethods($parent->alsoLoadMethods);
148 331
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
149 331
            $class->setFile($parent->getFile());
150 331
            if ($parent->isMappedSuperclass) {
151 278
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
152 278
            }
153 331
        }
154
155
        // Invoke driver
156
        try {
157 827
            $this->driver->loadMetadataForClass($class->getName(), $class);
158 827
        } catch (\ReflectionException $e) {
159
            throw MappingException::reflectionFailure($class->getName(), $e);
160
        }
161
162 824
        $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...
163
164 824
        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...
165 92
            if ($parent->generatorType) {
166 92
                $class->setIdGeneratorType($parent->generatorType);
167 92
            }
168 92
            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...
169
                $class->setIdGeneratorOptions($parent->generatorOptions);
170
            }
171 92
            if ($parent->idGenerator) {
172 92
                $class->setIdGenerator($parent->idGenerator);
173 92
            }
174 92
        } else {
175 824
            $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...ping\ClassMetadataInfo>. 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...
176
        }
177
178 824
        if ($parent && $parent->isInheritanceTypeSingleCollection()) {
179 79
            $class->setDatabase($parent->getDatabase());
180 79
            $class->setCollection($parent->getCollection());
181 79
        }
182
183 824
        $class->setParentClasses($nonSuperclassParents);
184
185 824 View Code Duplication
        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
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...
186 2
            $eventArgs = new LoadClassMetadataEventArgs($class, $this->dm);
187 2
            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
188 2
        }
189 824
    }
190
191
    /**
192
     * Validates the identifier mapping.
193
     *
194
     * @param ClassMetadata $class
195
     * @throws MappingException
196
     */
197 824
    protected function validateIdentifier($class)
198
    {
199 824
        if ( ! $class->identifier && ! $class->isMappedSuperclass && ! $class->isEmbeddedDocument && ! $class->isAggregationResultDocument) {
200
            throw MappingException::identifierRequired($class->name);
201
        }
202 824
    }
203
204
    /**
205
     * Creates a new ClassMetadata instance for the given class name.
206
     *
207
     * @param string $className
208
     * @return \Doctrine\ODM\MongoDB\Mapping\ClassMetadata
209
     */
210 827
    protected function newClassMetadataInstance($className)
211
    {
212 827
        return new ClassMetadata($className);
213
    }
214
215 824
    private function completeIdGeneratorMapping(ClassMetadataInfo $class)
216
    {
217 824
        $idGenOptions = $class->generatorOptions;
218 824
        switch ($class->generatorType) {
219 824
            case ClassMetadata::GENERATOR_TYPE_AUTO:
220 759
                $class->setIdGenerator(new AutoGenerator());
221 759
                break;
222 132
            case ClassMetadata::GENERATOR_TYPE_INCREMENT:
223 6
                $incrementGenerator = new IncrementGenerator();
224 6
                if (isset($idGenOptions['key'])) {
225
                    $incrementGenerator->setKey($idGenOptions['key']);
226
                }
227 6
                if (isset($idGenOptions['collection'])) {
228
                    $incrementGenerator->setCollection($idGenOptions['collection']);
229
                }
230 6
                $class->setIdGenerator($incrementGenerator);
231 6
                break;
232 126
            case ClassMetadata::GENERATOR_TYPE_UUID:
233 4
                $uuidGenerator = new UuidGenerator();
234 4
                isset($idGenOptions['salt']) && $uuidGenerator->setSalt($idGenOptions['salt']);
235 4
                $class->setIdGenerator($uuidGenerator);
236 4
                break;
237 122
            case ClassMetadata::GENERATOR_TYPE_ALNUM:
238 1
                $alnumGenerator = new AlnumGenerator();
239 1
                if (isset($idGenOptions['pad'])) {
240
                    $alnumGenerator->setPad($idGenOptions['pad']);
241
                }
242 1
                if (isset($idGenOptions['chars'])) {
243 1
                    $alnumGenerator->setChars($idGenOptions['chars']);
244 1
                } elseif (isset($idGenOptions['awkwardSafe'])) {
245
                    $alnumGenerator->setAwkwardSafeMode($idGenOptions['awkwardSafe']);
246
                }
247
248 1
                $class->setIdGenerator($alnumGenerator);
249 1
                break;
250 121
            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
251
                if (empty($idGenOptions['class'])) {
252
                    throw MappingException::missingIdGeneratorClass($class->name);
253
                }
254
255
                $customGenerator = new $idGenOptions['class'];
256
                unset($idGenOptions['class']);
257
                if ( ! $customGenerator instanceof AbstractIdGenerator) {
258
                    throw MappingException::classIsNotAValidGenerator(get_class($customGenerator));
259
                }
260
261
                $methods = get_class_methods($customGenerator);
262
                foreach ($idGenOptions as $name => $value) {
263
                    $method = 'set' . ucfirst($name);
264
                    if ( ! in_array($method, $methods)) {
265
                        throw MappingException::missingGeneratorSetter(get_class($customGenerator), $name);
266
                    }
267
268
                    $customGenerator->$method($value);
269
                }
270
                $class->setIdGenerator($customGenerator);
271
                break;
272 121
            case ClassMetadata::GENERATOR_TYPE_NONE;
273 121
                break;
274
            default:
275
                throw new MappingException('Unknown generator type: ' . $class->generatorType);
276 824
        }
277 824
    }
278
279
    /**
280
     * Adds inherited fields to the subclass mapping.
281
     *
282
     * @param ClassMetadata $subClass
283
     * @param ClassMetadata $parentClass
284
     */
285 331
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
286
    {
287 331
        foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
288 108 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...
289 103
                $mapping['inherited'] = $parentClass->name;
290 103
            }
291 108
            if ( ! isset($mapping['declared'])) {
292 108
                $mapping['declared'] = $parentClass->name;
293 108
            }
294 108
            $subClass->addInheritedFieldMapping($mapping);
295 331
        }
296 331
        foreach ($parentClass->reflFields as $name => $field) {
297 108
            $subClass->reflFields[$name] = $field;
298 331
        }
299 331
    }
300
301
302
    /**
303
     * Adds inherited association mappings to the subclass mapping.
304
     *
305
     * @param \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $subClass
306
     * @param \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $parentClass
307
     *
308
     * @return void
309
     *
310
     * @throws MappingException
311
     */
312 331
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
313
    {
314 331
        foreach ($parentClass->associationMappings as $field => $mapping) {
315 76
            if ($parentClass->isMappedSuperclass) {
316 3
                $mapping['sourceDocument'] = $subClass->name;
317 3
            }
318
319 76 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...
320 73
                $mapping['inherited'] = $parentClass->name;
321 73
            }
322 76
            if ( ! isset($mapping['declared'])) {
323 76
                $mapping['declared'] = $parentClass->name;
324 76
            }
325 76
            $subClass->addInheritedAssociationMapping($mapping);
326 331
        }
327 331
    }
328
329
    /**
330
     * Adds inherited indexes to the subclass mapping.
331
     *
332
     * @param ClassMetadata $subClass
333
     * @param ClassMetadata $parentClass
334
     */
335 331
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass)
336
    {
337 331
        foreach ($parentClass->indexes as $index) {
338 45
            $subClass->addIndex($index['keys'], $index['options']);
339 331
        }
340 331
    }
341
}
342