Completed
Pull Request — master (#923)
by Asmir
04:23 queued 01:58
created

DeserializationGraphNavigator   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Test Coverage

Coverage 82.35%

Importance

Changes 0
Metric Value
dl 0
loc 185
ccs 70
cts 85
cp 0.8235
rs 9
c 0
b 0
f 0
wmc 35

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 3
A resolveMetadata() 0 14 2
D accept() 0 113 27
A afterVisitingObject() 0 11 3
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 implements GraphNavigatorInterface
48
{
49
    /**
50
     * @var ExpressionLanguageExclusionStrategy
51
     */
52
    private $expressionExclusionStrategy;
53
54
    private $dispatcher;
55
    private $metadataFactory;
56
    private $handlerRegistry;
57
    private $objectConstructor;
58
    /**
59
     * @var AccessorStrategyInterface
60
     */
61
    private $accessor;
62
63 317
    public function __construct(
64
        MetadataFactoryInterface $metadataFactory,
65
        HandlerRegistryInterface $handlerRegistry,
66
        ObjectConstructorInterface $objectConstructor,
67
        AccessorStrategyInterface $accessor,
68
        EventDispatcherInterface $dispatcher = null,
69
        ExpressionEvaluatorInterface $expressionEvaluator = null
70
    ) {
71 317
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
72 317
        $this->metadataFactory = $metadataFactory;
73 317
        $this->handlerRegistry = $handlerRegistry;
74 317
        $this->objectConstructor = $objectConstructor;
75 317
        $this->accessor = $accessor;
76 317
        if ($expressionEvaluator) {
77 21
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
78
        }
79 317
    }
80
81
    /**
82
     * Called for each node of the graph that is being traversed.
83
     *
84
     * @param mixed $data the data depends on the direction, and type of visitor
85
     * @param null|array $type array has the format ["name" => string, "params" => array]
86
     * @param Context|DeserializationContext $context
87
     * @return mixed the return value depends on the direction, and type of visitor
88
     */
89 130
    public function accept($data, array $type = null, Context $context)
90
    {
91 130
        $visitor = $context->getVisitor();
92
93
        // If the type was not given, we infer the most specific type from the
94
        // input data in serialization mode.
95 130
        if (null === $type) {
96
            throw new RuntimeException('The type must be given for all properties when deserializing.');
97
        }
98
        // Sometimes data can convey null but is not of a null type.
99
        // Visitors can have the power to add this custom null evaluation
100 130
        if ($visitor instanceof NullAwareVisitorInterface && $visitor->isNull($data) === true) {
101 13
            $type = array('name' => 'NULL', 'params' => array());
102
        }
103
104 130
        switch ($type['name']) {
105 130
            case 'NULL':
106 15
                return $visitor->visitNull($data, $type);
107
108 116
            case 'string':
109 46
                return $visitor->visitString($data, $type);
110
111 111
            case 'int':
112 111
            case 'integer':
113 17
                return $visitor->visitInteger($data, $type);
114
115 107
            case 'bool':
116 107
            case 'boolean':
117 13
                return $visitor->visitBoolean($data, $type);
118
119 99
            case 'double':
120 93
            case 'float':
121 25
                return $visitor->visitDouble($data, $type);
122
123 87
            case 'array':
124 36
                return $visitor->visitArray($data, $type);
125
126 75
            case 'resource':
127
                throw new RuntimeException('Resources are not supported in serialized data.');
128
129
            default:
130
131 75
                $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

131
                $context->/** @scrutinizer ignore-call */ 
132
                          increaseDepth();
Loading history...
132
133
                // Trigger pre-serialization callbacks, and listeners if they exist.
134
                // Dispatch pre-serialization event before handling data to have ability change type in listener
135 75
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $context->getFormat())) {
136
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $context->getFormat(), $event = new PreDeserializeEvent($context, $data, $type));
137
                    $type = $event->getType();
138
                    $data = $event->getData();
139
                }
140
141
                // First, try whether a custom handler exists for the given type. This is done
142
                // before loading metadata because the type name might not be a class, but
143
                // could also simply be an artifical type.
144 75
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $context->getFormat())) {
145 26
                    $rs = \call_user_func($handler, $visitor, $data, $type, $context);
146 26
                    $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

146
                    $context->/** @scrutinizer ignore-call */ 
147
                              decreaseDepth();
Loading history...
147
148 26
                    return $rs;
149
                }
150
151 64
                $exclusionStrategy = $context->getExclusionStrategy();
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($visitor, $data, $metadata);
0 ignored issues
show
Bug introduced by
It seems like $visitor can also be of type JMS\Serializer\SerializationVisitorInterface; however, parameter $visitor of JMS\Serializer\Deseriali...ator::resolveMetadata() does only seem to accept JMS\Serializer\DeserializationVisitorInterface, 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

161
                    $metadata = $this->resolveMetadata(/** @scrutinizer ignore-type */ $visitor, $data, $metadata);
Loading history...
162
                }
163
164 62
                if ($exclusionStrategy->shouldSkipClass($metadata, $context)) {
165
                    $context->decreaseDepth();
166
167
                    return null;
168
                }
169
170 62
                $context->pushClassMetadata($metadata);
171
172 62
                $object = $this->objectConstructor->construct($visitor, $metadata, $data, $type, $context);
0 ignored issues
show
Bug introduced by
It seems like $visitor can also be of type JMS\Serializer\SerializationVisitorInterface; however, parameter $visitor of JMS\Serializer\Construct...rInterface::construct() does only seem to accept JMS\Serializer\DeserializationVisitorInterface, 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

172
                $object = $this->objectConstructor->construct(/** @scrutinizer ignore-type */ $visitor, $metadata, $data, $type, $context);
Loading history...
173
174 62
                $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 ($exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
177
                        continue;
178
                    }
179
180 62
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
181
                        continue;
182
                    }
183
184 62
                    if ($propertyMetadata->readOnly) {
185 17
                        continue;
186
                    }
187
188 59
                    $context->pushPropertyMetadata($propertyMetadata);
189
                    try {
190 59
                        $v = $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
                    $context->popPropertyMetadata();
196
                }
197
198 61
                $rs = $visitor->endVisitingObject($metadata, $data, $type);
199 61
                $this->afterVisitingObject($metadata, $rs, $type, $context);
200
201 61
                return $rs;
202
        }
203
    }
204
205 13
    private function resolveMetadata(DeserializationVisitorInterface $visitor, $data, ClassMetadata $metadata)
206
    {
207 13
        $typeValue = $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, Context $context): void
222
    {
223 61
        $context->decreaseDepth();
224 61
        $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, $context->getFormat())) {
231 1
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
232
        }
233 61
    }
234
}
235