Completed
Pull Request — master (#930)
by Asmir
02:34
created

SerializationGraphNavigator   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 208
Duplicated Lines 0 %

Test Coverage

Coverage 97.75%

Importance

Changes 0
Metric Value
dl 0
loc 208
ccs 87
cts 89
cp 0.9775
rs 8.295
c 0
b 0
f 0
wmc 42

4 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 4 1
A afterVisitingObject() 0 11 3
F accept() 0 134 35
A __construct() 0 14 3

How to fix   Complexity   

Complex Class

Complex classes like SerializationGraphNavigator 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 SerializationGraphNavigator, and based on these observations, apply Extract Interface, too.

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\GraphNavigator;
22
23
use JMS\Serializer\Accessor\AccessorStrategyInterface;
24
use JMS\Serializer\Context;
25
use JMS\Serializer\EventDispatcher\EventDispatcher;
26
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
27
use JMS\Serializer\EventDispatcher\ObjectEvent;
28
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
29
use JMS\Serializer\Exception\CircularReferenceDetectedException;
30
use JMS\Serializer\Exception\ExcludedClassException;
31
use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
32
use JMS\Serializer\Exception\NotAcceptableException;
33
use JMS\Serializer\Exception\RuntimeException;
34
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
35
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
36
use JMS\Serializer\GraphNavigator;
37
use JMS\Serializer\GraphNavigatorInterface;
38
use JMS\Serializer\Handler\HandlerRegistryInterface;
39
use JMS\Serializer\Metadata\ClassMetadata;
40
use JMS\Serializer\NullAwareVisitorInterface;
41
use JMS\Serializer\SerializationContext;
42
use JMS\Serializer\Visitor\SerializationVisitorInterface;
43
use JMS\Serializer\VisitorInterface;
44
use Metadata\MetadataFactoryInterface;
45
46
/**
47
 * Handles traversal along the object graph.
48
 *
49
 * This class handles traversal along the graph, and calls different methods
50
 * on visitors, or custom handlers to process its nodes.
51
 *
52
 * @author Johannes M. Schmitt <[email protected]>
53
 */
54
final class SerializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface
55
{
56
    /**
57
     * @var SerializationVisitorInterface
58
     */
59
    protected $visitor;
60
61
    /**
62
     * @var SerializationContext
63
     */
64
    protected $context;
65
66
    /**
67
     * @var ExpressionLanguageExclusionStrategy
68
     */
69
    private $expressionExclusionStrategy;
70
71
    private $dispatcher;
72
    private $metadataFactory;
73
    private $handlerRegistry;
74
    /**
75
     * @var AccessorStrategyInterface
76
     */
77
    private $accessor;
78
79
    /**
80
     * @var bool
81
     */
82
    private $shouldSerializeNull;
83
84 276
    public function __construct(
85
        MetadataFactoryInterface $metadataFactory,
86
        HandlerRegistryInterface $handlerRegistry,
87
        AccessorStrategyInterface $accessor,
88
        EventDispatcherInterface $dispatcher = null,
89
        ExpressionEvaluatorInterface $expressionEvaluator = null
90
    ) {
91 276
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
92 276
        $this->metadataFactory = $metadataFactory;
93 276
        $this->handlerRegistry = $handlerRegistry;
94 276
        $this->accessor = $accessor;
95
96 276
        if ($expressionEvaluator) {
97 23
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
98
        }
99 276
    }
100
101 276
    public function initialize(VisitorInterface $visitor, Context $context): void
102
    {
103 276
        parent::initialize($visitor, $context);
104 276
        $this->shouldSerializeNull = $context->shouldSerializeNull();
105 276
    }
106
107
    /**
108
     * Called for each node of the graph that is being traversed.
109
     *
110
     * @param mixed $data the data depends on the direction, and type of visitor
111
     * @param null|array $type array has the format ["name" => string, "params" => array]
112
     * @return mixed the return value depends on the direction, and type of visitor
113
     */
114 275
    public function accept($data, array $type = null)
115
    {
116
        // If the type was not given, we infer the most specific type from the
117
        // input data in serialization mode.
118 275
        if (null === $type) {
119
120 255
            $typeName = \gettype($data);
121 255
            if ('object' === $typeName) {
122 181
                $typeName = \get_class($data);
123
            }
124
125 255
            $type = ['name' => $typeName, 'params' => []];
126
        }
127
        // If the data is null, we have to force the type to null regardless of the input in order to
128
        // guarantee correct handling of null values, and not have any internal auto-casting behavior.
129 142
        else if (null === $data) {
130 5
            $type = ['name' => 'NULL', 'params' => []];
131
        }
132
        // Sometimes data can convey null but is not of a null type.
133
        // Visitors can have the power to add this custom null evaluation
134 275
        if ($this->visitor instanceof NullAwareVisitorInterface && $this->visitor->isNull($data) === true) {
135
            $type = ['name' => 'NULL', 'params' => []];
136
        }
137
138 275
        switch ($type['name']) {
139 275
            case 'NULL':
140 38
                if (!$this->shouldSerializeNull) {
141 15
                    throw new NotAcceptableException();
142
                }
143 23
                return $this->visitor->visitNull($data, $type);
144
145 251
            case 'string':
146 157
                return $this->visitor->visitString((string)$data, $type);
147
148 247
            case 'int':
149 247
            case 'integer':
150 42
                return $this->visitor->visitInteger((int)$data, $type);
151
152 244
            case 'bool':
153 244
            case 'boolean':
154 13
                return $this->visitor->visitBoolean((bool)$data, $type);
155
156 239
            case 'double':
157 230
            case 'float':
158 20
                return $this->visitor->visitDouble((float)$data, $type);
159
160 230
            case 'array':
161 100
                return $this->visitor->visitArray((array)$data, $type);
162
163 192
            case 'resource':
164 1
                $msg = 'Resources are not supported in serialized data.';
165 1
                if (null !== $path = $this->context->getPath()) {
166
                    $msg .= ' Path: ' . $path;
167
                }
168
169 1
                throw new RuntimeException($msg);
170
171
            default:
172
173 191
                if (null !== $data) {
174 191
                    if ($this->context->isVisiting($data)) {
175 4
                        throw new CircularReferenceDetectedException();
176
                    }
177 191
                    $this->context->startVisiting($data);
178
                }
179
180
                // If we're serializing a polymorphic type, then we'll be interested in the
181
                // metadata for the actual type of the object, not the base class.
182 191
                if (class_exists($type['name'], false) || interface_exists($type['name'], false)) {
183 191
                    if (is_subclass_of($data, $type['name'], false)) {
184 4
                        $type = ['name' => \get_class($data), 'params' => []];
185
                    }
186
                }
187
188
                // Trigger pre-serialization callbacks, and listeners if they exist.
189
                // Dispatch pre-serialization event before handling data to have ability change type in listener
190 191
                if ($this->dispatcher->hasListeners('serializer.pre_serialize', $type['name'], $this->format)) {
191 190
                    $this->dispatcher->dispatch('serializer.pre_serialize', $type['name'], $this->format, $event = new PreSerializeEvent($this->context, $data, $type));
192 190
                    $type = $event->getType();
193
                }
194
195
                // First, try whether a custom handler exists for the given type. This is done
196
                // before loading metadata because the type name might not be a class, but
197
                // could also simply be an artifical type.
198 191
                if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, $type['name'], $this->format)) {
199 47
                    $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context);
200 47
                    $this->context->stopVisiting($data);
201
202 47
                    return $rs;
203
                }
204
205
                /** @var $metadata ClassMetadata */
206 169
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
207
208 167
                if ($metadata->usingExpression && $this->expressionExclusionStrategy === null) {
209 2
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
210
                }
211
212 165
                if ($this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) {
213 10
                    $this->context->stopVisiting($data);
214
215 10
                    throw new ExcludedClassException();
216
                }
217
218 161
                $this->context->pushClassMetadata($metadata);
219
220 161
                foreach ($metadata->preSerializeMethods as $method) {
221 2
                    $method->invoke($data);
222
                }
223
224 161
                $this->visitor->startVisitingObject($metadata, $data, $type);
225 161
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
226 160
                    if ($this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
227 16
                        continue;
228
                    }
229
230 160
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) {
231 16
                        continue;
232
                    }
233
234 160
                    $v = $this->accessor->getValue($data, $propertyMetadata);
235
236 158
                    if (null === $v && $this->shouldSerializeNull !== true) {
237 22
                        continue;
238
                    }
239
240 156
                    $this->context->pushPropertyMetadata($propertyMetadata);
241 156
                    $this->visitor->visitProperty($propertyMetadata, $v);
242 155
                    $this->context->popPropertyMetadata();
243
                }
244
245 157
                $this->afterVisitingObject($metadata, $data, $type);
246
247 157
                return $this->visitor->endVisitingObject($metadata, $data, $type);
248
        }
249
    }
250
251 157
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type)
252
    {
253 157
        $this->context->stopVisiting($object);
254 157
        $this->context->popClassMetadata();
255
256 157
        foreach ($metadata->postSerializeMethods as $method) {
257 2
            $method->invoke($object);
258
        }
259
260 157
        if ($this->dispatcher->hasListeners('serializer.post_serialize', $metadata->name, $this->format)) {
261 2
            $this->dispatcher->dispatch('serializer.post_serialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type));
262
        }
263 157
    }
264
}
265