DeserializationGraphNavigator::accept()   F
last analyzed

Complexity

Conditions 36
Paths 309

Size

Total Lines 129
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 48.9521

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 36
eloc 74
nc 309
nop 2
dl 0
loc 129
ccs 51
cts 65
cp 0.7846
crap 48.9521
rs 1.9708
c 1
b 0
f 0

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\Type\Type;
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
 * @phpstan-import-type TypeArray from Type
39
 */
40
final class DeserializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface
41
{
42
    /**
43
     * @var DeserializationVisitorInterface
44
     */
45
    protected $visitor;
46
47
    /**
48
     * @var DeserializationContext
49
     */
50
    protected $context;
51
52
    /**
53
     * @var ExpressionLanguageExclusionStrategy
54
     */
55
    private $expressionExclusionStrategy;
56
57
    /**
58
     * @var EventDispatcherInterface
59
     */
60
    private $dispatcher;
61
62 141
    /**
63
     * @var MetadataFactoryInterface
64
     */
65
    private $metadataFactory;
66
67
    /**
68
     * @var HandlerRegistryInterface
69
     */
70 141
    private $handlerRegistry;
71 141
72 141
    /**
73 141
     * @var ObjectConstructorInterface
74 141
     */
75 141
    private $objectConstructor;
76 1
    /**
77
     * @var AccessorStrategyInterface
78 141
     */
79
    private $accessor;
80
81
    public function __construct(
82
        MetadataFactoryInterface $metadataFactory,
83
        HandlerRegistryInterface $handlerRegistry,
84
        ObjectConstructorInterface $objectConstructor,
85
        AccessorStrategyInterface $accessor,
86
        ?EventDispatcherInterface $dispatcher = null,
87 136
        ?ExpressionEvaluatorInterface $expressionEvaluator = null
88
    ) {
89
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
90
        $this->metadataFactory = $metadataFactory;
91 136
        $this->handlerRegistry = $handlerRegistry;
92
        $this->objectConstructor = $objectConstructor;
93
        $this->accessor = $accessor;
94
        if ($expressionEvaluator) {
95
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
96 136
        }
97 13
    }
98
99
    /**
100 136
     * Called for each node of the graph that is being traversed.
101 136
     *
102 15
     * @param mixed $data the data depends on the direction, and type of visitor
103
     * @param TypeArray|null $type array has the format ["name" => string, "params" => array]
0 ignored issues
show
Bug introduced by
The type JMS\Serializer\GraphNavigator\TypeArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
104 122
     *
105 51
     * @return mixed the return value depends on the direction, and type of visitor
106
     */
107 117
    public function accept($data, ?array $type = null)
108 117
    {
109 17
        // If the type was not given, we infer the most specific type from the
110
        // input data in serialization mode.
111 113
        if (null === $type) {
112 113
            throw new RuntimeException('The type must be given for all properties when deserializing.');
113 13
        }
114
115 105
        // Sometimes data can convey null but is not of a null type.
116 99
        // Visitors can have the power to add this custom null evaluation
117 25
        if ($this->visitor instanceof NullAwareVisitorInterface && true === $this->visitor->isNull($data)) {
118
            $type = ['name' => 'NULL', 'params' => []];
119 93
        }
120 36
121
        switch ($type['name']) {
122 81
            case 'NULL':
123
                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...
124
125
            case 'string':
126
                return $this->visitor->visitString($data, $type);
127 81
128
            case 'int':
129
            case 'integer':
130
                return $this->visitor->visitInteger($data, $type);
131 81
132
            case 'bool':
133
            case 'boolean':
134
            case 'false':
135
            case 'true':
136
                return $this->visitor->visitBoolean($data, $type);
137
138
            case 'double':
139
            case 'float':
140 81
                return $this->visitor->visitDouble($data, $type);
141 26
142 26
            case 'array':
143
            case 'iterable':
144 26
            case 'list':
145
                return $this->visitor->visitArray($data, $type);
146
147
            case 'resource':
148 70
                throw new RuntimeException('Resources are not supported in serialized data.');
149
150 70
            default:
151
                $this->context->increaseDepth();
152
153
                // Trigger pre-serialization callbacks, and listeners if they exist.
154 70
                // Dispatch pre-serialization event before handling data to have ability change type in listener
155 14
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $this->format)) {
156
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $this->format, $event = new PreDeserializeEvent($this->context, $data, $type));
157
                    $type = $event->getType();
158 68
                    $data = $event->getData();
159
                }
160
161
                // First, try whether a custom handler exists for the given type. This is done
162
                // before loading metadata because the type name might not be a class, but
163
                // could also simply be an artifical type.
164 68
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $this->format)) {
165
                    try {
166 68
                        $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

166
                        $rs = \call_user_func(/** @scrutinizer ignore-type */ $handler, $this->visitor, $data, $type, $this->context);
Loading history...
167
                        $this->context->decreaseDepth();
168 68
169 68
                        return $rs;
170 68
                    } catch (SkipHandlerException $e) {
171
                        // Skip handler, fallback to default behavior
172
                    }
173
                }
174 68
175
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
176
                \assert($metadata instanceof ClassMetadata);
177
178 68
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
179 18
                    throw new ExpressionLanguageRequiredException(sprintf('To use conditional exclude/expose in %s you must configure the expression language.', $metadata->name));
180
                }
181
182 64
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
183
                    $metadata = $this->resolveMetadata($data, $metadata);
184 64
                }
185 64
186 4
                if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
187
                    $this->context->decreaseDepth();
188
189 64
                    return null;
190
                }
191
192 67
                $this->context->pushClassMetadata($metadata);
193 67
194
                $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);
195 67
196
                if (null === $object) {
197
                    $this->context->popClassMetadata();
198
                    $this->context->decreaseDepth();
199 14
200
                    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...
201 14
                }
202
203 12
                $this->visitor->startVisitingObject($metadata, $object, $type);
204
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
205
                    if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
206
                        continue;
207
                    }
208
209
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
210
                        continue;
211
                    }
212 12
213
                    if ($propertyMetadata->readOnly) {
214
                        continue;
215 67
                    }
216
217 67
                    $this->context->pushPropertyMetadata($propertyMetadata);
218 67
                    try {
219
                        $v = $this->visitor->visitProperty($propertyMetadata, $data);
220 67
                        $this->accessor->setValue($object, $v, $propertyMetadata, $this->context);
221 4
                    } catch (NotAcceptableException $e) {
222
                        if (true === $propertyMetadata->hasDefault) {
223
                            $cloned = clone $propertyMetadata;
224 67
                            $cloned->setter = null;
225 1
                            $this->accessor->setValue($object, $cloned->defaultValue, $cloned, $this->context);
226
                        }
227 67
                    }
228
229
                    $this->context->popPropertyMetadata();
230
                }
231
232
                $rs = $this->visitor->endVisitingObject($metadata, $data, $type);
233
                $this->afterVisitingObject($metadata, $rs, $type);
234
235
                return $rs;
236
        }
237
    }
238
239
    /**
240
     * @param mixed $data
241
     */
242
    private function resolveMetadata($data, ClassMetadata $metadata): ?ClassMetadata
243
    {
244
        $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);
245
246
        if (!isset($metadata->discriminatorMap[$typeValue])) {
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
    /**
259
     * @param TypeArray $type
260
     */
261
    private function afterVisitingObject(ClassMetadata $metadata, object $object, array $type): void
262
    {
263
        $this->context->decreaseDepth();
264
        $this->context->popClassMetadata();
265
266
        foreach ($metadata->postDeserializeMethods as $method) {
267
            $method->invoke($object);
268
        }
269
270
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
271
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
272
        }
273
    }
274
}
275