Passed
Branch master (bf85d9)
by Johannes
05:40
created

DeserializationGraphNavigator   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Test Coverage

Coverage 84.54%

Importance

Changes 0
Metric Value
dl 0
loc 200
ccs 82
cts 97
cp 0.8454
rs 8.3673
c 0
b 0
f 0
wmc 45

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
C resolveMetadata() 0 40 13
D accept() 0 108 26
A afterVisitingObject() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like DeserializationGraphNavigator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DeserializationGraphNavigator, and based on these observations, apply Extract Interface, too.

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

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

136
                    $context->/** @scrutinizer ignore-call */ 
137
                              decreaseDepth();
Loading history...
137
138 25
                    return $rs;
139
                }
140
141 65
                $exclusionStrategy = $context->getExclusionStrategy();
142
143
                /** @var $metadata ClassMetadata */
144 65
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
145
146 65
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
147
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
148
                }
149
150 65
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
151 13
                    $metadata = $this->resolveMetadata($data, $metadata);
152
                }
153
154 63
                if ($exclusionStrategy->shouldSkipClass($metadata, $context)) {
155
                    $context->decreaseDepth();
156
157
                    return null;
158
                }
159
160 63
                $context->pushClassMetadata($metadata);
161
162 63
                $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

162
                $object = $this->objectConstructor->construct(/** @scrutinizer ignore-type */ $visitor, $metadata, $data, $type, $context);
Loading history...
163
164 63
                $visitor->startVisitingObject($metadata, $object, $type);
165 63
                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...
166 63
                    if ($exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
167
                        continue;
168
                    }
169
170 63
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
171
                        continue;
172
                    }
173
174 63
                    if ($propertyMetadata->readOnly) {
175 17
                        continue;
176
                    }
177
178 60
                    $context->pushPropertyMetadata($propertyMetadata);
179 60
                    $visitor->visitProperty($propertyMetadata, $data);
180 60
                    $context->popPropertyMetadata();
181
                }
182
183 62
                $rs = $visitor->endVisitingObject($metadata, $data, $type);
184 62
                $this->afterVisitingObject($metadata, $rs, $type, $context);
185
186 62
                return $rs;
187
        }
188
    }
189
190 13
    private function resolveMetadata($data, ClassMetadata $metadata)
191
    {
192
        switch (true) {
193 13
            case \is_array($data) && isset($data[$metadata->discriminatorFieldName]):
194 5
                $typeValue = (string)$data[$metadata->discriminatorFieldName];
195 5
                break;
196
197
            // Check XML attribute for discriminatorFieldName
198 8
            case \is_object($data) && $metadata->xmlDiscriminatorAttribute && isset($data[$metadata->discriminatorFieldName]):
199 1
                $typeValue = (string)$data[$metadata->discriminatorFieldName];
200 1
                break;
201
202
            // Check XML element with namespace for discriminatorFieldName
203 7
            case \is_object($data) && !$metadata->xmlDiscriminatorAttribute && null !== $metadata->xmlDiscriminatorNamespace && isset($data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}):
204 1
                $typeValue = (string)$data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName};
205 1
                break;
206
207
            // Check XML element for discriminatorFieldName
208 6
            case \is_object($data) && isset($data->{$metadata->discriminatorFieldName}):
209 4
                $typeValue = (string)$data->{$metadata->discriminatorFieldName};
210 4
                break;
211
212
            default:
213 2
                throw new LogicException(sprintf(
214 2
                    'The discriminator field name "%s" for base-class "%s" was not found in input data.',
215 2
                    $metadata->discriminatorFieldName,
216 2
                    $metadata->name
217
                ));
218
        }
219
220 11
        if (!isset($metadata->discriminatorMap[$typeValue])) {
221
            throw new LogicException(sprintf(
222
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
223
                $typeValue,
224
                $metadata->name,
225
                implode(', ', array_keys($metadata->discriminatorMap))
226
            ));
227
        }
228
229 11
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
230
    }
231
232 62
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context): void
233
    {
234 62
        $context->decreaseDepth();
235 62
        $context->popClassMetadata();
236
237 62
        foreach ($metadata->postDeserializeMethods as $method) {
238 4
            $method->invoke($object);
239
        }
240
241 62
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $context->getFormat())) {
242 1
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
243
        }
244 62
    }
245
}
246