Completed
Push — master ( 2b323a...f14e7e )
by Asmir
11:51
created

GraphNavigator::leaveScope()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 2
crap 3
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\EventDispatcher\ObjectEvent;
22
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
23
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
24
use JMS\Serializer\Exception\RuntimeException;
25
use JMS\Serializer\Construction\ObjectConstructorInterface;
26
use JMS\Serializer\Handler\HandlerRegistryInterface;
27
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
28
use JMS\Serializer\Metadata\ClassMetadata;
29
use Metadata\MetadataFactoryInterface;
30
use JMS\Serializer\Exception\InvalidArgumentException;
31
32
/**
33
 * Handles traversal along the object graph.
34
 *
35
 * This class handles traversal along the graph, and calls different methods
36
 * on visitors, or custom handlers to process its nodes.
37
 *
38
 * @author Johannes M. Schmitt <[email protected]>
39
 */
40
final class GraphNavigator
41
{
42
    const DIRECTION_SERIALIZATION = 1;
43
    const DIRECTION_DESERIALIZATION = 2;
44
45
    private $dispatcher;
46
    private $metadataFactory;
47
    private $handlerRegistry;
48
    private $objectConstructor;
49
50
    /**
51
     * Parses a direction string to one of the direction constants.
52
     *
53
     * @param string $dirStr
54
     *
55
     * @return integer
56
     */
57 6
    public static function parseDirection($dirStr)
58
    {
59 6
        switch (strtolower($dirStr)) {
60 6
            case 'serialization':
61 6
                return self::DIRECTION_SERIALIZATION;
62
63 3
            case 'deserialization':
64 3
                return self::DIRECTION_DESERIALIZATION;
65
66
            default:
67
                throw new InvalidArgumentException(sprintf('The direction "%s" does not exist.', $dirStr));
68
        }
69
    }
70
71 288
    public function __construct(MetadataFactoryInterface $metadataFactory, HandlerRegistryInterface $handlerRegistry, ObjectConstructorInterface $objectConstructor, EventDispatcherInterface $dispatcher = null)
72
    {
73 288
        $this->dispatcher = $dispatcher;
74 288
        $this->metadataFactory = $metadataFactory;
75 288
        $this->handlerRegistry = $handlerRegistry;
76 288
        $this->objectConstructor = $objectConstructor;
77 288
    }
78
79
    /**
80
     * Called for each node of the graph that is being traversed.
81
     *
82
     * @param mixed $data the data depends on the direction, and type of visitor
83
     * @param null|array $type array has the format ["name" => string, "params" => array]
84
     *
85
     * @return mixed the return value depends on the direction, and type of visitor
86
     */
87 272
    public function accept($data, array $type = null, Context $context)
88
    {
89 272
        $visitor = $context->getVisitor();
90
91
        // If the type was not given, we infer the most specific type from the
92
        // input data in serialization mode.
93 272
        if (null === $type) {
94 253
            if ($context instanceof DeserializationContext) {
95
                throw new RuntimeException('The type must be given for all properties when deserializing.');
96
            }
97
98 253
            $typeName = gettype($data);
99 253
            if ('object' === $typeName) {
100 178
                $typeName = get_class($data);
101 178
            }
102
103 253
            $type = array('name' => $typeName, 'params' => array());
104 257
        }
105
        // If the data is null, we have to force the type to null regardless of the input in order to
106
        // guarantee correct handling of null values, and not have any internal auto-casting behavior.
107 185
        else if ($context instanceof SerializationContext && null === $data) {
108 11
            $type = array('name' => 'NULL', 'params' => array());
109 11
        }
110
111 272
        switch ($type['name']) {
112 272
            case 'NULL':
113 41
                return $visitor->visitNull($data, $type, $context);
114
115 264
            case 'string':
116 141
                return $visitor->visitString($data, $type, $context);
117
118 257
            case 'int':
119 257
            case 'integer':
120 51
                return $visitor->visitInteger($data, $type, $context);
121
122 251
            case 'boolean':
123 22
                return $visitor->visitBoolean($data, $type, $context);
124
125 240
            case 'double':
126 240
            case 'float':
127 36
                return $visitor->visitDouble($data, $type, $context);
128
129 223
            case 'array':
130 92
                return $visitor->visitArray($data, $type, $context);
131
132 193
            case 'resource':
133 1
                $msg = 'Resources are not supported in serialized data.';
134 1
                if ($context instanceof SerializationContext && null !== $path = $context->getPath()) {
135
                    $msg .= ' Path: '.$path;
136
                }
137
138 1
                throw new RuntimeException($msg);
139
140 192
            default:
141
                // TODO: The rest of this method needs some refactoring.
142 192
                if ($context instanceof SerializationContext) {
143 176
                    if (null !== $data) {
144 176
                        if ($context->isVisiting($data)) {
145 3
                            return null;
146
                        }
147 176
                        $context->startVisiting($data);
148 176
                    }
149
150
                    // If we're serializing a polymorphic type, then we'll be interested in the
151
                    // metadata for the actual type of the object, not the base class.
152 176
                    if (class_exists($type['name'], false) || interface_exists($type['name'], false)) {
153 176
                        if (is_subclass_of($data, $type['name'], false)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $type['name'] can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
154 6
                            $type = array('name' => get_class($data), 'params' => array());
155 6
                        }
156 176
                    }
157 192
                } elseif ($context instanceof DeserializationContext) {
158 64
                    $context->increaseDepth();
159 64
                }
160
161
                // Trigger pre-serialization callbacks, and listeners if they exist.
162
                // Dispatch pre-serialization event before handling data to have ability change type in listener
163 192
                if ($context instanceof SerializationContext) {
164 176
                    if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.pre_serialize', $type['name'], $context->getFormat())) {
165 171
                        $this->dispatcher->dispatch('serializer.pre_serialize', $type['name'], $context->getFormat(), $event = new PreSerializeEvent($context, $data, $type));
166 171
                        $type = $event->getType();
167 171
                    }
168 192
                } elseif ($context instanceof DeserializationContext) {
169 64
                    if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $context->getFormat())) {
170
                        $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $context->getFormat(), $event = new PreDeserializeEvent($context, $data, $type));
171
                        $type = $event->getType();
172
                        $data = $event->getData();
173
                    }
174 64
                }
175
176
                // First, try whether a custom handler exists for the given type. This is done
177
                // before loading metadata because the type name might not be a class, but
178
                // could also simply be an artifical type.
179 192
                if (null !== $handler = $this->handlerRegistry->getHandler($context->getDirection(), $type['name'], $context->getFormat())) {
180 55
                    $rs = call_user_func($handler, $visitor, $data, $type, $context);
181 55
                    $this->leaveScope($context, $data);
182
183 55
                    return $rs;
184
                }
185
186 168
                $exclusionStrategy = $context->getExclusionStrategy();
187
188
                /** @var $metadata ClassMetadata */
189 168
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
190
191 165
                if ($context instanceof DeserializationContext && ! empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
192 10
                    $metadata = $this->resolveMetadata($data, $metadata);
193 8
                }
194
195 163
                if (null !== $exclusionStrategy && $exclusionStrategy->shouldSkipClass($metadata, $context)) {
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
196 3
                    $this->leaveScope($context, $data);
197
198 3
                    return null;
199
                }
200
201 163
                $context->pushClassMetadata($metadata);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
202
203 163
                if ($context instanceof SerializationContext) {
204 151
                    foreach ($metadata->preSerializeMethods as $method) {
205 3
                        $method->invoke($data);
206 151
                    }
207 151
                }
208
209 163
                $object = $data;
210 163
                if ($context instanceof DeserializationContext) {
211 54
                    $object = $this->objectConstructor->construct($visitor, $metadata, $data, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
212 54
                }
213
214 163
                if (isset($metadata->handlerCallbacks[$context->getDirection()][$context->getFormat()])) {
215 3
                    $rs = $object->{$metadata->handlerCallbacks[$context->getDirection()][$context->getFormat()]}(
216 3
                        $visitor,
217 3
                        $context instanceof SerializationContext ? null : $data,
218
                        $context
219 3
                    );
220 3
                    $this->afterVisitingObject($metadata, $object, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...MergeableClassMetadata>, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
221
222 3
                    return $context instanceof SerializationContext ? $rs : $object;
223
                }
224
225 160
                $visitor->startVisitingObject($metadata, $object, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...MergeableClassMetadata>, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
226 160
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
227 157
                    if (null !== $exclusionStrategy && $exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
228 13
                        continue;
229
                    }
230
231 157
                    if ($context instanceof DeserializationContext && $propertyMetadata->readOnly) {
232 14
                        continue;
233
                    }
234
235 157
                    $context->pushPropertyMetadata($propertyMetadata);
236 157
                    $visitor->visitProperty($propertyMetadata, $data, $context);
237 156
                    $context->popPropertyMetadata();
238 159
                }
239
240 157
                if ($context instanceof SerializationContext) {
241 146
                    $this->afterVisitingObject($metadata, $data, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...MergeableClassMetadata>, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
242
243 146
                    return $visitor->endVisitingObject($metadata, $data, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...MergeableClassMetadata>, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
244
                }
245
246 54
                $rs = $visitor->endVisitingObject($metadata, $data, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...MergeableClassMetadata>, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
247 54
                $this->afterVisitingObject($metadata, $rs, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...MergeableClassMetadata>, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
249 54
                return $rs;
250 192
        }
251
    }
252
253 10
    private function resolveMetadata($data, ClassMetadata $metadata)
254
    {
255 10
        switch (true) {
256 10
            case is_array($data) && isset($data[$metadata->discriminatorFieldName]):
257 5
                $typeValue = (string) $data[$metadata->discriminatorFieldName];
258 5
                break;
259
260 5
            // Check XML attribute for discriminatorFieldName
261 3
            case is_object($data) && $metadata->xmlDiscriminatorAttribute && isset($data[$metadata->discriminatorFieldName]):
262 3
                $typeValue = (string) $data[$metadata->discriminatorFieldName];
263
                break;
264 2
265 2
            case is_object($data) && isset($data->{$metadata->discriminatorFieldName}):
266 2
                $typeValue = (string) $data->{$metadata->discriminatorFieldName};
267 2
                break;
268 2
269 2
            default:
270 2
                throw new \LogicException(sprintf(
271
                    'The discriminator field name "%s" for base-class "%s" was not found in input data.',
272 8
                    $metadata->discriminatorFieldName,
273
                    $metadata->name
274
                ));
275
        }
276
277
        if ( ! isset($metadata->discriminatorMap[$typeValue])) {
278
            throw new \LogicException(sprintf(
279
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
280
                $typeValue,
281 8
                $metadata->name,
282
                implode(', ', array_keys($metadata->discriminatorMap))
283
            ));
284 184
        }
285
286 184
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
287 171
    }
288 184
289 61
    private function leaveScope(Context $context, $data)
290 61
    {
291 184
        if ($context instanceof SerializationContext) {
292
            $context->stopVisiting($data);
293 160
        } elseif ($context instanceof DeserializationContext) {
294
            $context->decreaseDepth();
295 160
        }
296 160
    }
297
298 160
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context)
299 149
    {
300 3
        $this->leaveScope($context, $object);
301 149
        $context->popClassMetadata();
302
303 149
        if ($context instanceof SerializationContext) {
304 2
            foreach ($metadata->postSerializeMethods as $method) {
305 2
                $method->invoke($object);
306
            }
307 149
308
            if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.post_serialize', $metadata->name, $context->getFormat())) {
309
                $this->dispatcher->dispatch('serializer.post_serialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
310 56
            }
311 4
312 56
            return;
313
        }
314 56
315 1
        foreach ($metadata->postDeserializeMethods as $method) {
316 1
            $method->invoke($object);
317 56
        }
318
319
        if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $context->getFormat())) {
320
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
321
        }
322
    }
323
}
324