Completed
Pull Request — master (#1303)
by Ayrton
06:25
created

DeserializationGraphNavigator::accept()   F

Complexity

Conditions 34
Paths 267

Size

Total Lines 128
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 44.5535

Importance

Changes 0
Metric Value
cc 34
eloc 71
c 0
b 0
f 0
nc 267
nop 2
dl 0
loc 128
ccs 53
cts 67
cp 0.791
crap 44.5535
rs 2.4958

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\RuntimeException;
18
use JMS\Serializer\Exception\SkipHandlerException;
19
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
20
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
21
use JMS\Serializer\GraphNavigator;
22
use JMS\Serializer\GraphNavigatorInterface;
23
use JMS\Serializer\Handler\HandlerRegistryInterface;
24
use JMS\Serializer\Metadata\ClassMetadata;
25
use JMS\Serializer\NullAwareVisitorInterface;
26
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
27
use Metadata\MetadataFactoryInterface;
28
29
/**
30
 * Handles traversal along the object graph.
31
 *
32
 * This class handles traversal along the graph, and calls different methods
33
 * on visitors, or custom handlers to process its nodes.
34
 *
35
 * @author Johannes M. Schmitt <[email protected]>
36
 */
37
final class DeserializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface
38
{
39
    /**
40
     * @var DeserializationVisitorInterface
41
     */
42
    protected $visitor;
43
44
    /**
45
     * @var DeserializationContext
46
     */
47
    protected $context;
48
49
    /**
50
     * @var ExpressionLanguageExclusionStrategy
51
     */
52
    private $expressionExclusionStrategy;
53
54
    /**
55
     * @var EventDispatcherInterface
56
     */
57
    private $dispatcher;
58
59
    /**
60
     * @var MetadataFactoryInterface
61
     */
62 141
    private $metadataFactory;
63
64
    /**
65
     * @var HandlerRegistryInterface
66
     */
67
    private $handlerRegistry;
68
69
    /**
70 141
     * @var ObjectConstructorInterface
71 141
     */
72 141
    private $objectConstructor;
73 141
    /**
74 141
     * @var AccessorStrategyInterface
75 141
     */
76 1
    private $accessor;
77
78 141
    public function __construct(
79
        MetadataFactoryInterface $metadataFactory,
80
        HandlerRegistryInterface $handlerRegistry,
81
        ObjectConstructorInterface $objectConstructor,
82
        AccessorStrategyInterface $accessor,
83
        ?EventDispatcherInterface $dispatcher = null,
84
        ?ExpressionEvaluatorInterface $expressionEvaluator = null
85
    ) {
86
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
87 136
        $this->metadataFactory = $metadataFactory;
88
        $this->handlerRegistry = $handlerRegistry;
89
        $this->objectConstructor = $objectConstructor;
90
        $this->accessor = $accessor;
91 136
        if ($expressionEvaluator) {
92
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
93
        }
94
    }
95
96 136
    /**
97 13
     * Called for each node of the graph that is being traversed.
98
     *
99
     * @param mixed $data the data depends on the direction, and type of visitor
100 136
     * @param array|null $type array has the format ["name" => string, "params" => array]
101 136
     *
102 15
     * @return mixed the return value depends on the direction, and type of visitor
103
     */
104 122
    public function accept($data, ?array $type = null)
105 51
    {
106
        // If the type was not given, we infer the most specific type from the
107 117
        // input data in serialization mode.
108 117
        if (null === $type) {
109 17
            throw new RuntimeException('The type must be given for all properties when deserializing.');
110
        }
111 113
112 113
        // Sometimes data can convey null but is not of a null type.
113 13
        // Visitors can have the power to add this custom null evaluation
114
        if ($this->visitor instanceof NullAwareVisitorInterface && true === $this->visitor->isNull($data)) {
115 105
            $type = ['name' => 'NULL', 'params' => []];
116 99
        }
117 25
118
        switch ($type['name']) {
119 93
            case 'NULL':
120 36
                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...
121
122 81
            case 'string':
123
                return $this->visitor->visitString($data, $type);
124
125
            case 'int':
126
            case 'integer':
127 81
                return $this->visitor->visitInteger($data, $type);
128
129
            case 'bool':
130
            case 'boolean':
131 81
                return $this->visitor->visitBoolean($data, $type);
132
133
            case 'double':
134
            case 'float':
135
                return $this->visitor->visitDouble($data, $type);
136
137
            case 'iterable':
138
                return $this->visitor->visitArray($data, $type);
139
140 81
            case 'array':
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)) {
0 ignored issues
show
introduced by
The condition null !== $handler = $thi...'name'], $this->format) is always true.
Loading history...
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
                    $nullOnUnknown = $metadata->discriminatorNullOnUnknown;
180
                    $metadata = $this->resolveMetadata($data, $metadata);
181
182 64
                    if ($nullOnUnknown && null === $metadata) {
183
                        return null;
184 64
                    }
185 64
                }
186 4
187
                if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
188
                    $this->context->decreaseDepth();
189 64
190
                    return null;
191
                }
192 67
193 67
                $this->context->pushClassMetadata($metadata);
194
195 67
                $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);
196
197
                if (null === $object) {
198
                    $this->context->popClassMetadata();
199 14
                    $this->context->decreaseDepth();
200
201 14
                    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...
202
                }
203 12
204
                $this->visitor->startVisitingObject($metadata, $object, $type);
205
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
206
                    if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
207
                        continue;
208
                    }
209
210
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
211
                        continue;
212 12
                    }
213
214
                    if ($propertyMetadata->readOnly) {
215 67
                        continue;
216
                    }
217 67
218 67
                    $this->context->pushPropertyMetadata($propertyMetadata);
219
                    try {
220 67
                        $v = $this->visitor->visitProperty($propertyMetadata, $data);
221 4
                        $this->accessor->setValue($object, $v, $propertyMetadata, $this->context);
222
                    } catch (NotAcceptableException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
223
                    }
224 67
225 1
                    $this->context->popPropertyMetadata();
226
                }
227 67
228
                $rs = $this->visitor->endVisitingObject($metadata, $data, $type);
229
                $this->afterVisitingObject($metadata, $rs, $type);
230
231
                return $rs;
232
        }
233
    }
234
235
    /**
236
     * @param mixed $data
237
     */
238
    private function resolveMetadata($data, ClassMetadata $metadata): ?ClassMetadata
239
    {
240
        $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);
241
242
        if (!isset($metadata->discriminatorMap[$typeValue])) {
243
            if ($metadata->discriminatorNullOnUnknown) {
244
                return null;
245
            }
246
247
            throw new LogicException(sprintf(
248
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
249
                $typeValue,
250
                $metadata->name,
251
                implode(', ', array_keys($metadata->discriminatorMap))
252
            ));
253
        }
254
255
        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...
256
    }
257
258
    private function afterVisitingObject(ClassMetadata $metadata, object $object, array $type): void
259
    {
260
        $this->context->decreaseDepth();
261
        $this->context->popClassMetadata();
262
263
        foreach ($metadata->postDeserializeMethods as $method) {
264
            $method->invoke($object);
265
        }
266
267
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
268
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
269
        }
270
    }
271
}
272