Passed
Pull Request — master (#920)
by Asmir
03:17
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 4
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
/*
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\Construction\ObjectConstructorInterface;
24
use JMS\Serializer\EventDispatcher\EventDispatcher;
25
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
26
use JMS\Serializer\EventDispatcher\ObjectEvent;
27
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
28
use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
29
use JMS\Serializer\Exception\LogicException;
30
use JMS\Serializer\Exception\RuntimeException;
31
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
32
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
33
use JMS\Serializer\Handler\HandlerRegistryInterface;
34
use JMS\Serializer\Metadata\ClassMetadata;
35
use Metadata\MetadataFactoryInterface;
36
37
/**
38
 * Handles traversal along the object graph.
39
 *
40
 * This class handles traversal along the graph, and calls different methods
41
 * on visitors, or custom handlers to process its nodes.
42
 *
43
 * @author Johannes M. Schmitt <[email protected]>
44
 */
45
final class DeserializationGraphNavigator implements GraphNavigatorInterface
46
{
47
    /**
48
     * @var ExpressionLanguageExclusionStrategy
49
     */
50
    private $expressionExclusionStrategy;
51
52
    private $dispatcher;
53
    private $metadataFactory;
54
    private $handlerRegistry;
55
    private $objectConstructor;
56
57 301
    public function __construct(
58
        MetadataFactoryInterface $metadataFactory,
59
        HandlerRegistryInterface $handlerRegistry,
60
        ObjectConstructorInterface $objectConstructor,
61
        EventDispatcherInterface $dispatcher = null,
62
        ExpressionEvaluatorInterface $expressionEvaluator = null
63
    ) {
64 301
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
65 301
        $this->metadataFactory = $metadataFactory;
66 301
        $this->handlerRegistry = $handlerRegistry;
67 301
        $this->objectConstructor = $objectConstructor;
68 301
        if ($expressionEvaluator) {
69 21
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
70
        }
71 301
    }
72
73
    /**
74
     * Called for each node of the graph that is being traversed.
75
     *
76
     * @param mixed $data the data depends on the direction, and type of visitor
77
     * @param null|array $type array has the format ["name" => string, "params" => array]
78
     * @param Context|DeserializationContext $context
79
     * @return mixed the return value depends on the direction, and type of visitor
80
     */
81 118
    public function accept($data, array $type = null, Context $context)
82
    {
83 118
        $visitor = $context->getVisitor();
84
85
        // If the type was not given, we infer the most specific type from the
86
        // input data in serialization mode.
87 118
        if (null === $type) {
88
            throw new RuntimeException('The type must be given for all properties when deserializing.');
89
        }
90
        // Sometimes data can convey null but is not of a null type.
91
        // Visitors can have the power to add this custom null evaluation
92 118
        if ($visitor instanceof NullAwareVisitorInterface && $visitor->isNull($data) === true) {
93 7
            $type = array('name' => 'NULL', 'params' => array());
94
        }
95
96 118
        switch ($type['name']) {
97 118
            case 'NULL':
98 8
                return $visitor->visitNull($data, $type);
99
100 111
            case 'string':
101 45
                return $visitor->visitString($data, $type);
102
103 107
            case 'int':
104 107
            case 'integer':
105 16
                return $visitor->visitInteger($data, $type);
106
107 104
            case 'bool':
108 104
            case 'boolean':
109 13
                return $visitor->visitBoolean($data, $type);
110
111 96
            case 'double':
112 91
            case 'float':
113 23
                return $visitor->visitDouble($data, $type);
114
115 86
            case 'array':
116 36
                return $visitor->visitArray($data, $type);
117
118 74
            case 'resource':
119
                throw new RuntimeException('Resources are not supported in serialized data.');
120
121
            default:
122
123 74
                $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

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

138
                    $context->/** @scrutinizer ignore-call */ 
139
                              decreaseDepth();
Loading history...
139
140 25
                    return $rs;
141
                }
142
143 64
                $exclusionStrategy = $context->getExclusionStrategy();
144
145
                /** @var $metadata ClassMetadata */
146 64
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
147
148 64
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
149
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
150
                }
151
152 64
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
153 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

153
                    $metadata = $this->resolveMetadata(/** @scrutinizer ignore-type */ $visitor, $data, $metadata);
Loading history...
154
                }
155
156 62
                if ($exclusionStrategy->shouldSkipClass($metadata, $context)) {
157
                    $context->decreaseDepth();
158
159
                    return null;
160
                }
161
162 62
                $context->pushClassMetadata($metadata);
163
164 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

164
                $object = $this->objectConstructor->construct(/** @scrutinizer ignore-type */ $visitor, $metadata, $data, $type, $context);
Loading history...
165
166 62
                $visitor->startVisitingObject($metadata, $object, $type);
167 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...
168 62
                    if ($exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
169
                        continue;
170
                    }
171
172 62
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
173
                        continue;
174
                    }
175
176 62
                    if ($propertyMetadata->readOnly) {
177 17
                        continue;
178
                    }
179
180 59
                    $context->pushPropertyMetadata($propertyMetadata);
181 59
                    $visitor->visitProperty($propertyMetadata, $data);
182 59
                    $context->popPropertyMetadata();
183
                }
184
185 61
                $rs = $visitor->endVisitingObject($metadata, $data, $type);
186 61
                $this->afterVisitingObject($metadata, $rs, $type, $context);
187
188 61
                return $rs;
189
        }
190
    }
191
192 13
    private function resolveMetadata(DeserializationVisitorInterface $visitor, $data, ClassMetadata $metadata)
193
    {
194 13
        $typeValue = $visitor->visitDiscriminatorMapProperty($data, $metadata);
195
196 11
        if (!isset($metadata->discriminatorMap[$typeValue])) {
197
            throw new LogicException(sprintf(
198
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
199
                $typeValue,
200
                $metadata->name,
201
                implode(', ', array_keys($metadata->discriminatorMap))
202
            ));
203
        }
204
205 11
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
206
    }
207
208 61
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context): void
209
    {
210 61
        $context->decreaseDepth();
211 61
        $context->popClassMetadata();
212
213 61
        foreach ($metadata->postDeserializeMethods as $method) {
214 4
            $method->invoke($object);
215
        }
216
217 61
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $context->getFormat())) {
218 1
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
219
        }
220 61
    }
221
}
222