Passed
Push — master ( e75d59...99d075 )
by Pavel
03:26
created

EntityMetadataFactory::resolveDiscriminatorValue()   D

Complexity

Conditions 10
Paths 8

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 29.4003

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 8
cts 19
cp 0.4211
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 16
nc 8
nop 1
crap 29.4003

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Bankiru\Api\Doctrine;
4
5
use Bankiru\Api\Doctrine\Exception\MappingException;
6
use Bankiru\Api\Doctrine\Mapping\EntityMetadata;
7
use Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory;
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
9
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
10
use Doctrine\Common\Persistence\Mapping\ReflectionService;
11
use ReflectionException;
12
13
class EntityMetadataFactory extends AbstractClassMetadataFactory
14
{
15
    /** @var  EntityManager */
16
    private $manager;
17
    /** @var  MappingDriver */
18
    private $driver;
19
20
    /** @var string[] */
21
    private $aliases = [];
22
23
    public function registerAlias($namespaceAlias, $namespace)
24
    {
25
        if (array_key_exists($namespaceAlias, $this->aliases)) {
26
            throw new \LogicException(sprintf('Alias "%s" is already registered', $namespaceAlias));
27
        }
28
29
        $this->aliases[$namespaceAlias] = rtrim($namespace, '\\');
30
    }
31
32
    /**
33
     * @param EntityManager $manager
34
     */
35 28
    public function setEntityManager($manager)
36
    {
37 28
        $this->manager = $manager;
38 28
    }
39
40
    /**
41
     * {@inheritDoc}
42
     */
43 28
    protected function loadMetadata($name)
44
    {
45 28
        $loaded = parent::loadMetadata($name);
46 28
        array_map([$this, 'resolveDiscriminatorValue'], array_map([$this, 'getMetadataFor'], $loaded));
47
48 28
        return $loaded;
49
    }
50
51
    /** {@inheritdoc} */
52 28
    protected function initialize()
53
    {
54 28
        $this->driver      = $this->manager->getConfiguration()->getDriver();
55 28
        $this->initialized = true;
56 28
    }
57
58
    /**
59
     * {@inheritdoc}
60
     * @throws MappingException
61
     */
62
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
63
    {
64
        if (!array_key_exists($namespaceAlias, $this->aliases)) {
65
            throw MappingException::unknownAlias($namespaceAlias);
66
        }
67
68
        return $this->aliases[$namespaceAlias] . $simpleClassName;
69
    }
70
71
    /** {@inheritdoc} */
72 28
    protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService)
73
    {
74 28
        if (!($class instanceof EntityMetadata)) {
75
            throw new \LogicException('Metadata is not supported');
76
        }
77
78
        /** @var EntityMetadata $class */
79 28
        $class->wakeupReflection($reflService);
80 28
    }
81
82
    /**
83
     * Initializes Reflection after ClassMetadata was constructed.
84
     *
85
     * @param ClassMetadata     $class
86
     * @param ReflectionService $reflService
87
     *
88
     * @return void
89
     */
90 28
    protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService)
91
    {
92 28
        if (!($class instanceof EntityMetadata)) {
93
            throw new \LogicException('Metadata is not supported');
94
        }
95
96
        /** @var EntityMetadata $class */
97 28
        $class->initializeReflection($reflService);
98 28
    }
99
100
    /**
101
     * Checks whether the class metadata is an entity.
102
     *
103
     * This method should return false for mapped superclasses or embedded classes.
104
     *
105
     * @param ClassMetadata $class
106
     *
107
     * @return boolean
108
     */
109 28
    protected function isEntity(ClassMetadata $class)
110
    {
111 28
        return true;
112
    }
113
114
    /** {@inheritdoc} */
115 28
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents)
116
    {
117
        /* @var $class EntityMetadata */
118
        /* @var $parent EntityMetadata */
119 28
        if ($parent) {
120 14
            $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<Bankiru\Api\Doctr...Mapping\EntityMetadata>. 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...
121 14
            $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<Bankiru\Api\Doctr...Mapping\EntityMetadata>. 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 14
            $class->setIdentifier($parent->identifier);
123 14
            $class->apiFactory     = $parent->apiFactory;
0 ignored issues
show
Bug introduced by
Accessing apiFactory 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...
124 14
            $class->clientName     = $parent->clientName;
0 ignored issues
show
Bug introduced by
Accessing clientName 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 14
            $class->methodProvider = $parent->methodProvider;
0 ignored issues
show
Bug introduced by
Accessing methodProvider 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...
126
127 14
            $class->setIdGeneratorType($parent->generatorType);
128 14
            $class->setDiscriminatorField($parent->discriminatorField);
129 14
            $class->setDiscriminatorMap($parent->discriminatorMap);
130
131 14
            if ($parent->isMappedSuperclass) {
132
                $class->setCustomRepositoryClass($parent->repositoryClass);
133
            }
134 14
        }
135
136
        // Invoke driver
137
        try {
138 28
            $this->getDriver()->loadMetadataForClass($class->getName(), $class);
139 28
        } catch (ReflectionException $e) {
140
            throw MappingException::nonExistingClass($class->getName());
141
        }
142
143 28
        if ($parent) {
144 14
            $classCache = $this->manager->getConfiguration()->getCacheConfiguration($class->getName());
145 14
            if (!$classCache->isEnabled()) {
146 14
                $parentCache = $this->manager->getConfiguration()->getCacheConfiguration($parent->getName());
147 14
                if ($parentCache->isEnabled()) {
148
                    $this->manager->getConfiguration()->setCacheConfigurationInstance($class->getName(), $parentCache);
149
                }
150 14
            }
151 14
        }
152
153 28
        if ($class->isRootEntity() && !$class->discriminatorMap) {
0 ignored issues
show
Bug introduced by
Accessing discriminatorMap 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 28
            $this->addDefaultDiscriminatorMap($class);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Bankiru\Api\Doctr...Mapping\EntityMetadata>. 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...
155 28
        }
156 28
    }
157
158
    /**
159
     * Returns the mapping driver implementation.
160
     *
161
     * @return \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver
162
     */
163 28
    protected function getDriver()
164
    {
165 28
        return $this->driver;
166
    }
167
168
    /**
169
     * Creates a new ClassMetadata instance for the given class name.
170
     *
171
     * @param string $className
172
     *
173
     * @return ClassMetadata
174
     */
175 28
    protected function newClassMetadataInstance($className)
176
    {
177 28
        return new EntityMetadata($className);
178
    }
179
180
    /**
181
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
182
     * map classes and looking for a fitting one.
183
     *
184
     * @param EntityMetadata $metadata
185
     *
186
     * @return void
187
     *
188
     * @throws MappingException
189
     */
190 28
    private function resolveDiscriminatorValue(EntityMetadata $metadata)
191
    {
192 28
        if ($metadata->discriminatorValue
193 4
            || !$metadata->discriminatorMap
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->discriminatorMap of type string[] 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...
194 4
            || $metadata->isMappedSuperclass
195 4
            || !$metadata->reflClass
196 4
            || $metadata->reflClass->isAbstract()
197 28
        ) {
198 28
            return;
199
        }
200
        // minor optimization: avoid loading related metadata when not needed
201
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
202
            if ($discriminatorClass === $metadata->name) {
203
                $metadata->discriminatorValue = $discriminatorValue;
0 ignored issues
show
Documentation Bug introduced by
The property $discriminatorValue was declared of type string, but $discriminatorValue is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
204
205
                return;
206
            }
207
        }
208
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
209
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
210
            if ($metadata->name === $this->getMetadataFor($discriminatorClass)->getName()) {
211
                $metadata->discriminatorValue = $discriminatorValue;
212
213
                return;
214
            }
215
        }
216
217
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->name, $metadata->rootEntityName);
218
    }
219
220
    /**
221
     * Adds a default discriminator map if no one is given
222
     *
223
     * If an entity is of any inheritance type and does not contain a
224
     * discriminator map, then the map is generated automatically. This process
225
     * is expensive computation wise.
226
     *
227
     * The automatically generated discriminator map contains the lowercase short name of
228
     * each class as key.
229
     *
230
     * @param EntityMetadata $class
231
     *
232
     * @throws MappingException
233
     */
234 28
    private function addDefaultDiscriminatorMap(EntityMetadata $class)
235
    {
236 28
        $allClasses = $this->driver->getAllClassNames();
237 28
        $fqcn       = $class->getName();
238 28
        $map        = [$this->getShortName($class->name) => $fqcn];
239 28
        $duplicates = [];
240 28
        foreach ($allClasses as $subClassCandidate) {
241 28
            if (is_subclass_of($subClassCandidate, $fqcn)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $fqcn can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
242 20
                $shortName = $this->getShortName($subClassCandidate);
243 20
                if (isset($map[$shortName])) {
244
                    $duplicates[] = $shortName;
245
                }
246 20
                $map[$shortName] = $subClassCandidate;
247 20
            }
248 28
        }
249 28
        if ($duplicates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $duplicates 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...
250
            throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
251
        }
252 28
        $class->setDiscriminatorMap($map);
253 28
    }
254
255
    /**
256
     * Gets the lower-case short name of a class.
257
     *
258
     * @param string $className
259
     *
260
     * @return string
261
     */
262 28
    private function getShortName($className)
263
    {
264 28
        if (strpos($className, "\\") === false) {
265
            return strtolower($className);
266
        }
267 28
        $parts = explode("\\", $className);
268
269 28
        return strtolower(end($parts));
270
    }
271
272
    /**
273
     * Adds inherited fields to the subclass mapping.
274
     *
275
     * @param EntityMetadata $subClass
276
     * @param EntityMetadata $parentClass
277
     *
278
     * @return void
279
     */
280 14
    private function addInheritedFields(EntityMetadata $subClass, EntityMetadata $parentClass)
281
    {
282 14 View Code Duplication
        foreach ($parentClass->fields as $mapping) {
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...
283 14
            if (!isset($mapping['inherited']) && !$parentClass->isMappedSuperclass) {
284 14
                $mapping['inherited'] = $parentClass->name;
285 14
            }
286 14
            if (!isset($mapping['declared'])) {
287 14
                $mapping['declared'] = $parentClass->name;
288 14
            }
289 14
            $subClass->addInheritedFieldMapping($mapping);
290 14
        }
291 14
        foreach ($parentClass->reflFields as $name => $field) {
292 14
            $subClass->reflFields[$name] = $field;
293 14
        }
294 14
    }
295
296
    /**
297
     * Adds inherited association mappings to the subclass mapping.
298
     *
299
     * @param EntityMetadata $subClass
300
     * @param EntityMetadata $parentClass
301
     *
302
     * @return void
303
     *
304
     * @throws MappingException
305
     */
306 14
    private function addInheritedRelations(EntityMetadata $subClass, EntityMetadata $parentClass)
307
    {
308 14 View Code Duplication
        foreach ($parentClass->associations as $mapping) {
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...
309 10
            if (!isset($mapping['inherited']) && !$parentClass->isMappedSuperclass) {
310 10
                $mapping['inherited'] = $parentClass->name;
311 10
            }
312 10
            if (!isset($mapping['declared'])) {
313 10
                $mapping['declared'] = $parentClass->name;
314 10
            }
315 10
            $subClass->addInheritedAssociationMapping($mapping);
316 14
        }
317 14
    }
318
}
319