Passed
Pull Request — master (#965)
by Asmir
02:38
created

afterVisitingObject()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
nop 3
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
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\Exclusion\ExpressionLanguageExclusionStrategy;
19
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
20
use JMS\Serializer\GraphNavigator;
21
use JMS\Serializer\GraphNavigatorInterface;
22
use JMS\Serializer\Handler\HandlerRegistryInterface;
23
use JMS\Serializer\Metadata\ClassMetadata;
24
use JMS\Serializer\NullAwareVisitorInterface;
25
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
26
use Metadata\MetadataFactoryInterface;
27
28
/**
29
 * Handles traversal along the object graph.
30
 *
31
 * This class handles traversal along the graph, and calls different methods
32
 * on visitors, or custom handlers to process its nodes.
33
 *
34
 * @author Johannes M. Schmitt <[email protected]>
35
 */
36
final class DeserializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface
37
{
38
    /**
39
     * @var DeserializationVisitorInterface
40
     */
41
    protected $visitor;
42
43
    /**
44
     * @var DeserializationContext
45
     */
46
    protected $context;
47
48
    /**
49
     * @var ExpressionLanguageExclusionStrategy
50
     */
51
    private $expressionExclusionStrategy;
52
53
    private $dispatcher;
54
    private $metadataFactory;
55
    private $handlerRegistry;
56
    private $objectConstructor;
57
    /**
58
     * @var AccessorStrategyInterface
59
     */
60
    private $accessor;
61
62 141
    public function __construct(
63
        MetadataFactoryInterface $metadataFactory,
64
        HandlerRegistryInterface $handlerRegistry,
65
        ObjectConstructorInterface $objectConstructor,
66
        AccessorStrategyInterface $accessor,
67
        EventDispatcherInterface $dispatcher = null,
68
        ExpressionEvaluatorInterface $expressionEvaluator = null
69
    ) {
70 141
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
71 141
        $this->metadataFactory = $metadataFactory;
72 141
        $this->handlerRegistry = $handlerRegistry;
73 141
        $this->objectConstructor = $objectConstructor;
74 141
        $this->accessor = $accessor;
75 141
        if ($expressionEvaluator) {
76 1
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
77
        }
78 141
    }
79
80
    /**
81
     * Called for each node of the graph that is being traversed.
82
     *
83
     * @param mixed $data the data depends on the direction, and type of visitor
84
     * @param null|array $type array has the format ["name" => string, "params" => array]
85
     * @return mixed the return value depends on the direction, and type of visitor
86
     */
87 136
    public function accept($data, array $type = null)
88
    {
89
        // If the type was not given, we infer the most specific type from the
90
        // input data in serialization mode.
91 136
        if (null === $type) {
92
            throw new RuntimeException('The type must be given for all properties when deserializing.');
93
        }
94
        // Sometimes data can convey null but is not of a null type.
95
        // Visitors can have the power to add this custom null evaluation
96 136
        if ($this->visitor instanceof NullAwareVisitorInterface && $this->visitor->isNull($data) === true) {
97 13
            $type = ['name' => 'NULL', 'params' => []];
98
        }
99
100 136
        switch ($type['name']) {
101 136
            case 'NULL':
102 15
                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...
103
104 122
            case 'string':
105 51
                return $this->visitor->visitString($data, $type);
106
107 117
            case 'int':
108 117
            case 'integer':
109 17
                return $this->visitor->visitInteger($data, $type);
110
111 113
            case 'bool':
112 113
            case 'boolean':
113 13
                return $this->visitor->visitBoolean($data, $type);
114
115 105
            case 'double':
116 99
            case 'float':
117 25
                return $this->visitor->visitDouble($data, $type);
118
119 93
            case 'array':
120 36
                return $this->visitor->visitArray($data, $type);
121
122 81
            case 'resource':
123
                throw new RuntimeException('Resources are not supported in serialized data.');
124
125
            default:
126
127 81
                $this->context->increaseDepth();
128
129
                // Trigger pre-serialization callbacks, and listeners if they exist.
130
                // Dispatch pre-serialization event before handling data to have ability change type in listener
131 81
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $this->format)) {
132
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $this->format, $event = new PreDeserializeEvent($this->context, $data, $type));
133
                    $type = $event->getType();
134
                    $data = $event->getData();
135
                }
136
137
                // First, try whether a custom handler exists for the given type. This is done
138
                // before loading metadata because the type name might not be a class, but
139
                // could also simply be an artifical type.
140 81
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $this->format)) {
141 26
                    $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context);
142 26
                    $this->context->decreaseDepth();
143
144 26
                    return $rs;
145
                }
146
147
                /** @var $metadata ClassMetadata */
148 70
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
149
150 70
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
151
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
152
                }
153
154 70
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
155 14
                    $metadata = $this->resolveMetadata($data, $metadata);
156
                }
157
158 68
                if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
159
                    $this->context->decreaseDepth();
160
161
                    return null;
162
                }
163
164 68
                $this->context->pushClassMetadata($metadata);
165
166 68
                $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);
167
168 68
                $this->visitor->startVisitingObject($metadata, $object, $type);
169 68
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
0 ignored issues
show
Bug introduced by
The property propertyMetadata does not seem to exist on Metadata\ClassHierarchyMetadata.
Loading history...
170 68
                    if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
171
                        continue;
172
                    }
173
174 68
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
175
                        continue;
176
                    }
177
178 68
                    if ($propertyMetadata->readOnly) {
179 18
                        continue;
180
                    }
181
182 64
                    $this->context->pushPropertyMetadata($propertyMetadata);
183
                    try {
184 64
                        $v = $this->visitor->visitProperty($propertyMetadata, $data);
185 64
                        $this->accessor->setValue($object, $v, $propertyMetadata, $this->context);
186 4
                    } catch (NotAcceptableException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
187
188
                    }
189 64
                    $this->context->popPropertyMetadata();
190
                }
191
192 67
                $rs = $this->visitor->endVisitingObject($metadata, $data, $type);
193 67
                $this->afterVisitingObject($metadata, $rs, $type);
194
195 67
                return $rs;
196
        }
197
    }
198
199 14
    private function resolveMetadata($data, ClassMetadata $metadata)
200
    {
201 14
        $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);
202
203 12
        if (!isset($metadata->discriminatorMap[$typeValue])) {
204
            throw new LogicException(sprintf(
205
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
206
                $typeValue,
207
                $metadata->name,
208
                implode(', ', array_keys($metadata->discriminatorMap))
209
            ));
210
        }
211
212 12
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
213
    }
214
215 67
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type): void
216
    {
217 67
        $this->context->decreaseDepth();
218 67
        $this->context->popClassMetadata();
219
220 67
        foreach ($metadata->postDeserializeMethods as $method) {
221 4
            $method->invoke($object);
222
        }
223
224 67
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
225 1
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
226
        }
227 67
    }
228
}
229