Passed
Pull Request — master (#1554)
by
unknown
02:54
created

DeserializationGraphNavigator::accept()   F

Complexity

Conditions 37
Paths 593

Size

Total Lines 130
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 50.0623

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 37
eloc 75
c 1
b 0
f 0
nc 593
nop 2
dl 0
loc 130
ccs 52
cts 66
cp 0.7879
crap 50.0623
rs 0.5652

How to fix   Long Method    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
declare(strict_types=1);
4
5
namespace JMS\Serializer\GraphNavigator;
6
7
use JMS\Serializer\Accessor\AccessorStrategyInterface;
8
use JMS\Serializer\Construction\ObjectConstructorInterface;
9
use JMS\Serializer\DeserializationContext;
10
use JMS\Serializer\EventDispatcher\EventDispatcher;
11
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
12
use JMS\Serializer\EventDispatcher\ObjectEvent;
13
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
14
use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
15
use JMS\Serializer\Exception\LogicException;
16
use JMS\Serializer\Exception\NotAcceptableException;
17
use JMS\Serializer\Exception\PropertyMissingException;
18
use JMS\Serializer\Exception\RuntimeException;
19
use JMS\Serializer\Exception\SkipHandlerException;
20
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
21
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
22
use JMS\Serializer\GraphNavigator;
23
use JMS\Serializer\GraphNavigatorInterface;
24
use JMS\Serializer\Handler\HandlerRegistryInterface;
25
use JMS\Serializer\Metadata\ClassMetadata;
26
use JMS\Serializer\NullAwareVisitorInterface;
27
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
28
use Metadata\MetadataFactoryInterface;
29
30
/**
31
 * Handles traversal along the object graph.
32
 *
33
 * This class handles traversal along the graph, and calls different methods
34
 * on visitors, or custom handlers to process its nodes.
35
 *
36
 * @author Johannes M. Schmitt <[email protected]>
37
 */
38
final class DeserializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface
39
{
40
    /**
41
     * @var DeserializationVisitorInterface
42
     */
43
    protected $visitor;
44
45
    /**
46
     * @var DeserializationContext
47
     */
48
    protected $context;
49
50
    /**
51
     * @var ExpressionLanguageExclusionStrategy
52
     */
53
    private $expressionExclusionStrategy;
54
55
    /**
56
     * @var EventDispatcherInterface
57
     */
58
    private $dispatcher;
59
60
    /**
61
     * @var MetadataFactoryInterface
62 141
     */
63
    private $metadataFactory;
64
65
    /**
66
     * @var HandlerRegistryInterface
67
     */
68
    private $handlerRegistry;
69
70 141
    /**
71 141
     * @var ObjectConstructorInterface
72 141
     */
73 141
    private $objectConstructor;
74 141
    /**
75 141
     * @var AccessorStrategyInterface
76 1
     */
77
    private $accessor;
78 141
79
    public function __construct(
80
        MetadataFactoryInterface $metadataFactory,
81
        HandlerRegistryInterface $handlerRegistry,
82
        ObjectConstructorInterface $objectConstructor,
83
        AccessorStrategyInterface $accessor,
84
        ?EventDispatcherInterface $dispatcher = null,
85
        ?ExpressionEvaluatorInterface $expressionEvaluator = null
86
    ) {
87 136
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
88
        $this->metadataFactory = $metadataFactory;
89
        $this->handlerRegistry = $handlerRegistry;
90
        $this->objectConstructor = $objectConstructor;
91 136
        $this->accessor = $accessor;
92
        if ($expressionEvaluator) {
93
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
94
        }
95
    }
96 136
97 13
    /**
98
     * Called for each node of the graph that is being traversed.
99
     *
100 136
     * @param mixed $data the data depends on the direction, and type of visitor
101 136
     * @param array|null $type array has the format ["name" => string, "params" => array]
102 15
     *
103
     * @return mixed the return value depends on the direction, and type of visitor
104 122
     */
105 51
    public function accept($data, ?array $type = null)
106
    {
107 117
        // If the type was not given, we infer the most specific type from the
108 117
        // input data in serialization mode.
109 17
        if (null === $type) {
110
            throw new RuntimeException('The type must be given for all properties when deserializing.');
111 113
        }
112 113
113 13
        // Sometimes data can convey null but is not of a null type.
114
        // Visitors can have the power to add this custom null evaluation
115 105
        if ($this->visitor instanceof NullAwareVisitorInterface && true === $this->visitor->isNull($data)) {
116 99
            $type = ['name' => 'NULL', 'params' => []];
117 25
        }
118
119 93
        switch ($type['name']) {
120 36
            case 'NULL':
121
                return $this->visitor->visitNull($data, $type);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->visitor->visitNull($data, $type) targeting JMS\Serializer\Visitor\D...rInterface::visitNull() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
122 81
123
            case 'string':
124
                return $this->visitor->visitString($data, $type);
125
126
            case 'int':
127 81
            case 'integer':
128
                return $this->visitor->visitInteger($data, $type);
129
130
            case 'bool':
131 81
            case 'boolean':
132
                return $this->visitor->visitBoolean($data, $type);
133
134
            case 'double':
135
            case 'float':
136
                return $this->visitor->visitDouble($data, $type);
137
138
            case 'array':
139
            case 'iterable':
140 81
            case 'list':
141 26
                return $this->visitor->visitArray($data, $type);
142 26
143
            case 'resource':
144 26
                throw new RuntimeException('Resources are not supported in serialized data.');
145
146
            default:
147
                $this->context->increaseDepth();
148 70
149
                // Trigger pre-serialization callbacks, and listeners if they exist.
150 70
                // Dispatch pre-serialization event before handling data to have ability change type in listener
151
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $this->format)) {
152
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $this->format, $event = new PreDeserializeEvent($this->context, $data, $type));
153
                    $type = $event->getType();
154 70
                    $data = $event->getData();
155 14
                }
156
157
                // First, try whether a custom handler exists for the given type. This is done
158 68
                // before loading metadata because the type name might not be a class, but
159
                // could also simply be an artifical type.
160
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $this->format)) {
161
                    try {
162
                        $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context);
0 ignored issues
show
Bug introduced by
It seems like $handler can also be of type object; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
                        $rs = \call_user_func(/** @scrutinizer ignore-type */ $handler, $this->visitor, $data, $type, $this->context);
Loading history...
163
                        $this->context->decreaseDepth();
164 68
165
                        return $rs;
166 68
                    } catch (SkipHandlerException $e) {
167
                        // Skip handler, fallback to default behavior
168 68
                    }
169 68
                }
170 68
171
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
172
                \assert($metadata instanceof ClassMetadata);
173
174 68
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
175
                    throw new ExpressionLanguageRequiredException(sprintf('To use conditional exclude/expose in %s you must configure the expression language.', $metadata->name));
176
                }
177
178 68
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
179 18
                    $metadata = $this->resolveMetadata($data, $metadata);
180
                }
181
182 64
                if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
183
                    $this->context->decreaseDepth();
184 64
185 64
                    return null;
186 4
                }
187
188
                $this->context->pushClassMetadata($metadata);
189 64
190
                $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);
191
192 67
                if (null === $object) {
193 67
                    $this->context->popClassMetadata();
194
                    $this->context->decreaseDepth();
195 67
196
                    return $this->visitor->visitNull($data, $type);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->visitor->visitNull($data, $type) targeting JMS\Serializer\Visitor\D...rInterface::visitNull() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
197
                }
198
199 14
                $this->visitor->startVisitingObject($metadata, $object, $type);
200
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
201 14
                    $allowsNull = null === $propertyMetadata->type ? true : $this->allowsNull($propertyMetadata->type);
202
                    if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
203 12
                        continue;
204
                    }
205
206
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
207
                        continue;
208
                    }
209
210
                    if ($propertyMetadata->readOnly) {
211
                        continue;
212 12
                    }
213
214
                    $this->context->pushPropertyMetadata($propertyMetadata);
215 67
                    try {
216
                        $v = $this->visitor->visitProperty($propertyMetadata, $data);
217 67
                        $this->accessor->setValue($object, $v, $propertyMetadata, $this->context);
218 67
                    } catch (NotAcceptableException $e) {
219
                        if (true === $propertyMetadata->hasDefault) {
220 67
                            $cloned = clone $propertyMetadata;
221 4
                            $cloned->setter = null;
222
                            $this->accessor->setValue($object, $cloned->defaultValue, $cloned, $this->context);
223
                        } elseif (!$allowsNull && $this->context->getRequireAllRequiredProperties()) {
224 67
                            throw new PropertyMissingException('Property ' . $propertyMetadata->name . ' is missing from data');
225 1
                        }
226
                    }
227 67
228
                    $this->context->popPropertyMetadata();
229
                }
230
231
                $rs = $this->visitor->endVisitingObject($metadata, $data, $type);
232
                $this->afterVisitingObject($metadata, $rs, $type);
233
234
                return $rs;
235
        }
236
    }
237
238
    /**
239
     * @param mixed $data
240
     */
241
    private function resolveMetadata($data, ClassMetadata $metadata): ?ClassMetadata
242
    {
243
        $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);
244
245
        if (!isset($metadata->discriminatorMap[$typeValue])) {
246
            throw new LogicException(sprintf(
247
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
248
                $typeValue,
249
                $metadata->name,
250
                implode(', ', array_keys($metadata->discriminatorMap)),
251
            ));
252
        }
253
254
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->metadataFa...minatorMap[$typeValue]) could return the type Metadata\ClassHierarchyMetadata which is incompatible with the type-hinted return JMS\Serializer\Metadata\ClassMetadata|null. Consider adding an additional type-check to rule them out.
Loading history...
255
    }
256
257
    private function afterVisitingObject(ClassMetadata $metadata, object $object, array $type): void
258
    {
259
        $this->context->decreaseDepth();
260
        $this->context->popClassMetadata();
261
262
        foreach ($metadata->postDeserializeMethods as $method) {
263
            $method->invoke($object);
264
        }
265
266
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
267
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
268
        }
269
    }
270
271
    private function allowsNull(array $type): bool
272
    {
273
        $allowsNull = false;
274
        if ('union' === $type['name'] && isset($type['params'][0])) {
275
            foreach ($type['params'] as $param) {
276
                if ('NULL' === $param['name']) {
277
                    $allowsNull = true;
278
                }
279
            }
280
        } elseif ('NULL' === $type['name']) {
281
            $allowsNull = true;
282
        }
283
284
        return $allowsNull;
285
    }
286
}
287