Passed
Pull Request — master (#925)
by Asmir
02:44
created

DeserializationGraphNavigator::resolveMetadata()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.686

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 2
dl 0
loc 14
ccs 4
cts 9
cp 0.4444
crap 2.686
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
namespace JMS\Serializer;
22
23
use JMS\Serializer\Accessor\AccessorStrategyInterface;
24
use JMS\Serializer\Construction\ObjectConstructorInterface;
25
use JMS\Serializer\EventDispatcher\EventDispatcher;
26
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
27
use JMS\Serializer\EventDispatcher\ObjectEvent;
28
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
29
use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
30
use JMS\Serializer\Exception\LogicException;
31
use JMS\Serializer\Exception\NotAcceptableException;
32
use JMS\Serializer\Exception\RuntimeException;
33
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
34
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
35
use JMS\Serializer\Handler\HandlerRegistryInterface;
36
use JMS\Serializer\Metadata\ClassMetadata;
37
use Metadata\MetadataFactoryInterface;
38
39
/**
40
 * Handles traversal along the object graph.
41
 *
42
 * This class handles traversal along the graph, and calls different methods
43
 * on visitors, or custom handlers to process its nodes.
44
 *
45
 * @author Johannes M. Schmitt <[email protected]>
46
 */
47
final class DeserializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface
48
{
49
    /**
50
     * @var DeserializationVisitorInterface
51
     */
52
    protected $visitor;
53
54
    /**
55
     * @var ExpressionLanguageExclusionStrategy
56
     */
57
    private $expressionExclusionStrategy;
58
59
    private $dispatcher;
60
    private $metadataFactory;
61
    private $handlerRegistry;
62
    private $objectConstructor;
63
    /**
64
     * @var AccessorStrategyInterface
65
     */
66
    private $accessor;
67
68 135
    public function __construct(
69
        MetadataFactoryInterface $metadataFactory,
70
        HandlerRegistryInterface $handlerRegistry,
71
        ObjectConstructorInterface $objectConstructor,
72
        AccessorStrategyInterface $accessor,
73
        EventDispatcherInterface $dispatcher = null,
74
        ExpressionEvaluatorInterface $expressionEvaluator = null
75
    ) {
76 135
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
77 135
        $this->metadataFactory = $metadataFactory;
78 135
        $this->handlerRegistry = $handlerRegistry;
79 135
        $this->objectConstructor = $objectConstructor;
80 135
        $this->accessor = $accessor;
81 135
        if ($expressionEvaluator) {
82 1
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
83
        }
84 135
    }
85
86
    /**
87
     * Called for each node of the graph that is being traversed.
88
     *
89
     * @param mixed $data the data depends on the direction, and type of visitor
90
     * @param null|array $type array has the format ["name" => string, "params" => array]
91
     * @return mixed the return value depends on the direction, and type of visitor
92
     */
93 130
    public function accept($data, array $type = null)
94
    {
95
        // If the type was not given, we infer the most specific type from the
96
        // input data in serialization mode.
97 130
        if (null === $type) {
98
            throw new RuntimeException('The type must be given for all properties when deserializing.');
99
        }
100
        // Sometimes data can convey null but is not of a null type.
101
        // Visitors can have the power to add this custom null evaluation
102 130
        if ($this->visitor instanceof NullAwareVisitorInterface && $this->visitor->isNull($data) === true) {
103 13
            $type = array('name' => 'NULL', 'params' => array());
104
        }
105
106 130
        switch ($type['name']) {
107 130
            case 'NULL':
108 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\Deseriali...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...
109
110 116
            case 'string':
111 46
                return $this->visitor->visitString($data, $type);
112
113 111
            case 'int':
114 111
            case 'integer':
115 17
                return $this->visitor->visitInteger($data, $type);
116
117 107
            case 'bool':
118 107
            case 'boolean':
119 13
                return $this->visitor->visitBoolean($data, $type);
120
121 99
            case 'double':
122 93
            case 'float':
123 25
                return $this->visitor->visitDouble($data, $type);
124
125 87
            case 'array':
126 36
                return $this->visitor->visitArray($data, $type);
127
128 75
            case 'resource':
129
                throw new RuntimeException('Resources are not supported in serialized data.');
130
131
            default:
132
133 75
                $this->context->increaseDepth();
0 ignored issues
show
Bug introduced by
The method increaseDepth() does not exist on JMS\Serializer\Context. It seems like you code against a sub-type of JMS\Serializer\Context such as JMS\Serializer\DeserializationContext. ( Ignorable by Annotation )

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

133
                $this->context->/** @scrutinizer ignore-call */ 
134
                                increaseDepth();
Loading history...
134
135
                // Trigger pre-serialization callbacks, and listeners if they exist.
136
                // Dispatch pre-serialization event before handling data to have ability change type in listener
137 75
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $this->format)) {
138
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $this->format, $event = new PreDeserializeEvent($this->context, $data, $type));
139
                    $type = $event->getType();
140
                    $data = $event->getData();
141
                }
142
143
                // First, try whether a custom handler exists for the given type. This is done
144
                // before loading metadata because the type name might not be a class, but
145
                // could also simply be an artifical type.
146 75
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $this->format)) {
147 26
                    $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context);
148 26
                    $this->context->decreaseDepth();
0 ignored issues
show
Bug introduced by
The method decreaseDepth() does not exist on JMS\Serializer\Context. It seems like you code against a sub-type of JMS\Serializer\Context such as JMS\Serializer\DeserializationContext. ( Ignorable by Annotation )

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

148
                    $this->context->/** @scrutinizer ignore-call */ 
149
                                    decreaseDepth();
Loading history...
149
150 26
                    return $rs;
151
                }
152
153
                /** @var $metadata ClassMetadata */
154 64
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
155
156 64
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
157
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
158
                }
159
160 64
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
161 13
                    $metadata = $this->resolveMetadata($data, $metadata);
162
                }
163
164 62
                if ($this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
165
                    $this->context->decreaseDepth();
166
167
                    return null;
168
                }
169
170 62
                $this->context->pushClassMetadata($metadata);
171
172 62
                $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context);
173
174 62
                $this->visitor->startVisitingObject($metadata, $object, $type);
175 62
                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...
176 62
                    if ($this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
177
                        continue;
178
                    }
179
180 62
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
181
                        continue;
182
                    }
183
184 62
                    if ($propertyMetadata->readOnly) {
185 17
                        continue;
186
                    }
187
188 59
                    $this->context->pushPropertyMetadata($propertyMetadata);
189
                    try {
190 59
                        $v = $this->visitor->visitProperty($propertyMetadata, $data);
191 59
                        $this->accessor->setValue($object, $v, $propertyMetadata);
192 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...
193
194
                    }
195 59
                    $this->context->popPropertyMetadata();
196
                }
197
198 61
                $rs = $this->visitor->endVisitingObject($metadata, $data, $type);
199 61
                $this->afterVisitingObject($metadata, $rs, $type);
200
201 61
                return $rs;
202
        }
203
    }
204
205 13
    private function resolveMetadata($data, ClassMetadata $metadata)
206
    {
207 13
        $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);
208
209 11
        if (!isset($metadata->discriminatorMap[$typeValue])) {
210
            throw new LogicException(sprintf(
211
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
212
                $typeValue,
213
                $metadata->name,
214
                implode(', ', array_keys($metadata->discriminatorMap))
215
            ));
216
        }
217
218 11
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
219
    }
220
221 61
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type): void
222
    {
223 61
        $this->context->decreaseDepth();
224 61
        $this->context->popClassMetadata();
225
226 61
        foreach ($metadata->postDeserializeMethods as $method) {
227 4
            $method->invoke($object);
228
        }
229
230 61
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
231 1
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
232
        }
233 61
    }
234
}
235