Completed
Push — master ( c0c1af...f39d8b )
by Asmir
9s
created

XmlDeserializationVisitor::visitProperty()   C

Complexity

Conditions 11
Paths 12

Size

Total Lines 66
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 11.0121

Importance

Changes 0
Metric Value
dl 0
loc 66
ccs 41
cts 43
cp 0.9535
rs 5.9447
c 0
b 0
f 0
cc 11
eloc 40
nc 12
nop 3
crap 11.0121

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Exception\XmlErrorException;
22
use JMS\Serializer\Exception\LogicException;
23
use JMS\Serializer\Exception\InvalidArgumentException;
24
use JMS\Serializer\Exception\RuntimeException;
25
use JMS\Serializer\Metadata\PropertyMetadata;
26
use JMS\Serializer\Metadata\ClassMetadata;
27
28
class XmlDeserializationVisitor extends AbstractVisitor
29
{
30
    private $objectStack;
31
    private $metadataStack;
32
    private $objectMetadataStack;
33
    private $currentObject;
34
    private $currentMetadata;
35
    private $result;
36
    private $navigator;
37
    private $disableExternalEntities = true;
38
    private $doctypeWhitelist = array();
39
40
    public function enableExternalEntities()
41
    {
42
        $this->disableExternalEntities = false;
43
    }
44
45 51
    public function setNavigator(GraphNavigator $navigator)
46
    {
47 51
        $this->navigator = $navigator;
48 51
        $this->objectStack = new \SplStack;
49 51
        $this->metadataStack = new \SplStack;
50 51
        $this->objectMetadataStack = new \SplStack;
51 51
        $this->result = null;
52 51
    }
53
54 1
    public function getNavigator()
55
    {
56 1
        return $this->navigator;
57
    }
58
59 53
    public function prepare($data)
60
    {
61 53
        $previous = libxml_use_internal_errors(true);
62 53
        $previousEntityLoaderState = libxml_disable_entity_loader($this->disableExternalEntities);
63
64 53
        if (false !== stripos($data, '<!doctype')) {
65 3
            $internalSubset = $this->getDomDocumentTypeEntitySubset($data);
66 3
            if (!in_array($internalSubset, $this->doctypeWhitelist, true)) {
67 2
                throw new InvalidArgumentException(sprintf(
68 2
                    'The document type "%s" is not allowed. If it is safe, you may add it to the whitelist configuration.',
69
                    $internalSubset
70 2
                ));
71
            }
72 1
        }
73
74 51
        $doc = simplexml_load_string($data);
75
76 51
        libxml_use_internal_errors($previous);
77 51
        libxml_disable_entity_loader($previousEntityLoaderState);
78
79 51
        if (false === $doc) {
80
            throw new XmlErrorException(libxml_get_last_error());
81
        }
82
83 51
        return $doc;
84
    }
85
86 1
    public function visitNull($data, array $type, Context $context)
87
    {
88 1
        return null;
89
    }
90
91 19
    public function visitString($data, array $type, Context $context)
92
    {
93 19
        $data = (string) $data;
94
95 19
        if (null === $this->result) {
96 3
            $this->result = $data;
97 3
        }
98
99 19
        return $data;
100
    }
101
102 8
    public function visitBoolean($data, array $type, Context $context)
103
    {
104 8
        $data = (string) $data;
105
106 8
        if ('true' === $data || '1' === $data) {
107 4
            $data = true;
108 8
        } elseif ('false' === $data || '0' === $data) {
109 5
            $data = false;
110 5
        } else {
111
            throw new RuntimeException(sprintf('Could not convert data to boolean. Expected "true", "false", "1" or "0", but got %s.', json_encode($data)));
112
        }
113
114 8
        if (null === $this->result) {
115 6
            $this->result = $data;
116 6
        }
117
118 8
        return $data;
119
    }
120
121 8
    public function visitInteger($data, array $type, Context $context)
122
    {
123 8
        $data = (integer) $data;
124
125 8
        if (null === $this->result) {
126 2
            $this->result = $data;
127 2
        }
128
129 8
        return $data;
130
    }
131
132 12
    public function visitDouble($data, array $type, Context $context)
133
    {
134 12
        $data = (double) $data;
135
136 12
        if (null === $this->result) {
137 6
            $this->result = $data;
138 6
        }
139
140 12
        return $data;
141
    }
142
143 14
    public function visitArray($data, array $type, Context $context)
144
    {
145 14
        $entryName = null !== $this->currentMetadata && $this->currentMetadata->xmlEntryName ? $this->currentMetadata->xmlEntryName : 'entry';
146 14
        $namespace = null !== $this->currentMetadata && $this->currentMetadata->xmlEntryNamespace ? $this->currentMetadata->xmlEntryNamespace : null;
147
148 14
        if ($namespace === null && $this->objectMetadataStack->count()) {
149 9
            $classMetadata = $this->objectMetadataStack->top();
150 9
            $namespace = isset($classMetadata->xmlNamespaces[''])?$classMetadata->xmlNamespaces['']:$namespace;
151 9
        }
152
153 14
        if (0 === $data->count()){
154 1
            $hasNode = false;
155 1
        } else {
156 14
            $hasNode = null !== $namespace ? isset($data->children($namespace)->$entryName) : isset($data->$entryName);
157
        }
158
159 14
        if (false === $hasNode) {
160 1
            if (null === $this->result) {
161
                return $this->result = array();
162
            }
163
164 1
            return array();
165
        }
166
167 14
        switch (count($type['params'])) {
168 14
            case 0:
169
                throw new RuntimeException(sprintf('The array type must be specified either as "array<T>", or "array<K,V>".'));
170
171 14
            case 1:
172 14
                $result = array();
173
174 14
                if (null === $this->result) {
175 5
                    $this->result = &$result;
176 5
                }
177
178 14
                $nodes = $data->children($namespace)->$entryName;
179 14
                foreach ($nodes as $v) {
180 14
                    $result[] = $this->navigator->accept($v, $type['params'][0], $context);
181 14
                }
182
183 14
                return $result;
184
185 3
            case 2:
186 3
                if (null === $this->currentMetadata) {
187
                    throw new RuntimeException('Maps are not supported on top-level without metadata.');
188
                }
189
190 3
                list($keyType, $entryType) = $type['params'];
191 3
                $result = array();
192 3
                if (null === $this->result) {
193
                    $this->result = &$result;
194
                }
195
196 3
                $nodes = $data->children($namespace)->$entryName;
197 3
                foreach ($nodes as $v) {
198 3
                    $attrs = $v->attributes();
199 3
                    if ( ! isset($attrs[$this->currentMetadata->xmlKeyAttribute])) {
200
                        throw new RuntimeException(sprintf('The key attribute "%s" must be set for each entry of the map.', $this->currentMetadata->xmlKeyAttribute));
201
                    }
202
203 3
                    $k = $this->navigator->accept($attrs[$this->currentMetadata->xmlKeyAttribute], $keyType, $context);
204 3
                    $result[$k] = $this->navigator->accept($v, $entryType, $context);
205 3
                }
206
207 3
                return $result;
208
209
            default:
210
                throw new LogicException(sprintf('The array type does not support more than 2 parameters, but got %s.', json_encode($type['params'])));
211
        }
212
    }
213
214 24
    public function startVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context)
215
    {
216 24
        $this->setCurrentObject($object);
217 24
        $this->objectMetadataStack->push($metadata);
218 24
        if (null === $this->result) {
219 23
            $this->result = $this->currentObject;
220 23
        }
221 24
    }
222
223 23
    public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
224
    {
225 23
        $name = $this->namingStrategy->translateName($metadata);
226
227 23
        if ( ! $metadata->type) {
228
            throw new RuntimeException(sprintf('You must define a type for %s::$%s.', $metadata->reflection->class, $metadata->name));
229
        }
230
231 23
       if ($metadata->xmlAttribute) {
232
233 5
            $attributes = $data->attributes($metadata->xmlNamespace);
234 5
            if (isset($attributes[$name])) {
235 5
                $v = $this->navigator->accept($attributes[$name], $metadata->type, $context);
236 5
                $metadata->setValue($this->currentObject, $v);
237 5
            }
238
239 5
            return;
240
        }
241
242 23
        if ($metadata->xmlValue) {
243 6
            $v = $this->navigator->accept($data, $metadata->type, $context);
244 6
            $metadata->setValue($this->currentObject, $v);
245
246 6
            return;
247
        }
248
249 21
        if ($metadata->xmlCollection) {
250 5
            $enclosingElem = $data;
251 5
            if (!$metadata->xmlCollectionInline) {
252 5
                $enclosingElem = $data->children($metadata->xmlNamespace)->$name;
253 5
            }
254
255 5
            $this->setCurrentMetadata($metadata);
256 5
            $v = $this->navigator->accept($enclosingElem, $metadata->type, $context);
257 5
            $this->revertCurrentMetadata();
258 5
            $metadata->setValue($this->currentObject, $v);
259
260 5
            return;
261
        }
262
263 20
        if ($metadata->xmlNamespace) {
264 3
            $node = $data->children($metadata->xmlNamespace)->$name;
265 3
            if (!$node->count()) {
266
                return;
267
            }
268 3
        } else {
269
270 18
            $namespaces = $data->getDocNamespaces();
271
272 18
            if (isset($namespaces[''])) {
273 1
                $prefix = uniqid('ns-');
274 1
                $data->registerXPathNamespace($prefix, $namespaces['']);
275 1
                $nodes = $data->xpath('./'.$prefix. ':'.$name );
276 1
            } else {
277 17
                $nodes = $data->xpath('./'. $name );
278
            }
279 18
            if (empty($nodes)) {
280 1
                return;
281
            }
282 18
            $node = reset($nodes);
283
        }
284
285 20
        $v = $this->navigator->accept($node, $metadata->type, $context);
286
287 20
        $metadata->setValue($this->currentObject, $v);
288 20
    }
289
290 24
    public function endVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
291
    {
292 24
        $rs = $this->currentObject;
293 24
        $this->objectMetadataStack->pop();
294 24
        $this->revertCurrentObject();
295
296 24
        return $rs;
297
    }
298
299 24
    public function setCurrentObject($object)
300
    {
301 24
        $this->objectStack->push($this->currentObject);
302 24
        $this->currentObject = $object;
303 24
    }
304
305
    public function getCurrentObject()
306
    {
307
        return $this->currentObject;
308
    }
309
310 24
    public function revertCurrentObject()
311
    {
312 24
        return $this->currentObject = $this->objectStack->pop();
313
    }
314
315 5
    public function setCurrentMetadata(PropertyMetadata $metadata)
316
    {
317 5
        $this->metadataStack->push($this->currentMetadata);
318 5
        $this->currentMetadata = $metadata;
319 5
    }
320
321
    public function getCurrentMetadata()
322
    {
323
        return $this->currentMetadata;
324
    }
325
326 5
    public function revertCurrentMetadata()
327
    {
328 5
        return $this->currentMetadata = $this->metadataStack->pop();
329
    }
330
331 50
    public function getResult()
332
    {
333 50
        return $this->result;
334
    }
335
336
    /**
337
     * @param array<string> $doctypeWhitelist
338
     */
339 1
    public function setDoctypeWhitelist(array $doctypeWhitelist)
340
    {
341 1
        $this->doctypeWhitelist = $doctypeWhitelist;
342 1
    }
343
344
    /**
345
     * @return array<string>
346
     */
347
    public function getDoctypeWhitelist()
348
    {
349
        return $this->doctypeWhitelist;
350
    }
351
352
    /**
353
     * Retrieves internalSubset even in bugfixed php versions
354
     *
355
     * @param \DOMDocumentType $child
0 ignored issues
show
Bug introduced by
There is no parameter named $child. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
356
     * @param string $data
357
     * @return string
358
     */
359 3
    private function getDomDocumentTypeEntitySubset($data)
360
    {
361 3
        $startPos = $endPos = stripos($data, '<!doctype');
362 3
        $braces = 0;
363
        do {
364 3
            $char = $data[$endPos++];
365 3
            if ($char === '<') {
366 3
                ++$braces;
367 3
            }
368 3
            if ($char === '>') {
369 3
                --$braces;
370 3
            }
371 3
        } while ($braces > 0);
372
373 3
        $internalSubset = substr($data, $startPos, $endPos - $startPos);
374 3
        $internalSubset = str_replace(array("\n", "\r"), '', $internalSubset);
375 3
        $internalSubset = preg_replace('/\s{2,}/', ' ', $internalSubset);
376 3
        $internalSubset = str_replace(array("[ <!", "> ]>"), array('[<!', '>]>'), $internalSubset);
377
378 3
        return $internalSubset;
379
    }
380
}
381