Completed
Push — master ( a093b2...6b8644 )
by Marcin
18s queued 14s
created

DeserializationGraphNavigator::accept()   F

Complexity

Conditions 36
Paths 309

Size

Total Lines 129
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 47.8316

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 36
eloc 74
c 1
b 0
f 0
nc 309
nop 2
dl 0
loc 129
ccs 53
cts 67
cp 0.791
crap 47.8316
rs 1.9708

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
            case 'false':
132
            case 'true':
133
                return $this->visitor->visitBoolean($data, $type);
134
135
            case 'double':
136
            case 'float':
137
                return $this->visitor->visitDouble($data, $type);
138
139
            case 'array':
140 81
            case 'iterable':
141 26
            case 'list':
142 26
                return $this->visitor->visitArray($data, $type);
143
144 26
            case 'resource':
145
                throw new RuntimeException('Resources are not supported in serialized data.');
146
147
            default:
148 70
                $this->context->increaseDepth();
149
150 70
                // Trigger pre-serialization callbacks, and listeners if they exist.
151
                // Dispatch pre-serialization event before handling data to have ability change type in listener
152
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $this->format)) {
153
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $this->format, $event = new PreDeserializeEvent($this->context, $data, $type));
154 70
                    $type = $event->getType();
155 14
                    $data = $event->getData();
156
                }
157
158 68
                // First, try whether a custom handler exists for the given type. This is done
159
                // before loading metadata because the type name might not be a class, but
160
                // could also simply be an artifical type.
161
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $this->format)) {
162
                    try {
163
                        $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

163
                        $rs = \call_user_func(/** @scrutinizer ignore-type */ $handler, $this->visitor, $data, $type, $this->context);
Loading history...
164 68
                        $this->context->decreaseDepth();
165
166 68
                        return $rs;
167
                    } catch (SkipHandlerException $e) {
168 68
                        // Skip handler, fallback to default behavior
169 68
                    }
170 68
                }
171
172
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
173
                \assert($metadata instanceof ClassMetadata);
174 68
175
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
176
                    throw new ExpressionLanguageRequiredException(sprintf('To use conditional exclude/expose in %s you must configure the expression language.', $metadata->name));
177
                }
178 68
179 18
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
180
                    $metadata = $this->resolveMetadata($data, $metadata);
181
                }
182 64
183
                if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
184 64
                    $this->context->decreaseDepth();
185 64
186 4
                    return null;
187
                }
188
189 64
                $this->context->pushClassMetadata($metadata);
190
191
                $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);
192 67
193 67
                if (null === $object) {
194
                    $this->context->popClassMetadata();
195 67
                    $this->context->decreaseDepth();
196
197
                    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...
198
                }
199 14
200
                $this->visitor->startVisitingObject($metadata, $object, $type);
201 14
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
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
                        }
224 67
                    }
225 1
226
                    $this->context->popPropertyMetadata();
227 67
                }
228
229
                $rs = $this->visitor->endVisitingObject($metadata, $data, $type);
230
                $this->afterVisitingObject($metadata, $rs, $type);
231
232
                return $rs;
233
        }
234
    }
235
236
    /**
237
     * @param mixed $data
238
     */
239
    private function resolveMetadata($data, ClassMetadata $metadata): ?ClassMetadata
240
    {
241
        $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);
242
243
        if (!isset($metadata->discriminatorMap[$typeValue])) {
244
            throw new LogicException(sprintf(
245
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
246
                $typeValue,
247
                $metadata->name,
248
                implode(', ', array_keys($metadata->discriminatorMap)),
249
            ));
250
        }
251
252
        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...
253
    }
254
255
    private function afterVisitingObject(ClassMetadata $metadata, object $object, array $type): void
256
    {
257
        $this->context->decreaseDepth();
258
        $this->context->popClassMetadata();
259
260
        foreach ($metadata->postDeserializeMethods as $method) {
261
            $method->invoke($object);
262
        }
263
264
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
265
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
266
        }
267
    }
268
}
269