Completed
Push — master ( f2200d...30d0b7 )
by Asmir
15:12 queued 11:20
created

XmlDeserializationVisitor::getResult()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 55
    public function setNavigator(GraphNavigator $navigator)
46
    {
47 55
        $this->navigator = $navigator;
48 55
        $this->objectStack = new \SplStack;
49 55
        $this->metadataStack = new \SplStack;
50 55
        $this->objectMetadataStack = new \SplStack;
51 55
        $this->result = null;
52 55
    }
53
54 1
    public function getNavigator()
55
    {
56 1
        return $this->navigator;
57
    }
58
59 57
    public function prepare($data)
60
    {
61 57
        $previous = libxml_use_internal_errors(true);
62 57
        $previousEntityLoaderState = libxml_disable_entity_loader($this->disableExternalEntities);
63
64 57
        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 55
        $doc = simplexml_load_string($data);
75
76 55
        libxml_use_internal_errors($previous);
77 55
        libxml_disable_entity_loader($previousEntityLoaderState);
78
79 55
        if (false === $doc) {
80
            throw new XmlErrorException(libxml_get_last_error());
81
        }
82
83 55
        return $doc;
84
    }
85
86 1
    public function visitNull($data, array $type, Context $context)
87
    {
88 1
        return null;
89
    }
90
91 20
    public function visitString($data, array $type, Context $context)
92
    {
93 20
        $data = (string) $data;
94
95 20
        if (null === $this->result) {
96 3
            $this->result = $data;
97 3
        }
98
99 20
        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 15
    public function visitArray($data, array $type, Context $context)
144
    {
145 15
        $entryName = null !== $this->currentMetadata && $this->currentMetadata->xmlEntryName ? $this->currentMetadata->xmlEntryName : 'entry';
146 15
        $namespace = null !== $this->currentMetadata && $this->currentMetadata->xmlEntryNamespace ? $this->currentMetadata->xmlEntryNamespace : null;
147
148 15
        if ($namespace === null && $this->objectMetadataStack->count()) {
149 10
            $classMetadata = $this->objectMetadataStack->top();
150 10
            $namespace = isset($classMetadata->xmlNamespaces[''])?$classMetadata->xmlNamespaces['']:$namespace;
151 10
        }
152
153 15
        if (null !== $namespace) {
154 4
            $prefix = uniqid('ns-');
155 4
            $data->registerXPathNamespace($prefix, $namespace);
156 4
            $nodes = $data->xpath("$prefix:$entryName");
157 4
        } else {
158 12
            $nodes = $data->xpath($entryName);
159
        }
160
161 15
        if (!count($nodes)) {
162 2
            if (null === $this->result) {
163
                return $this->result = array();
164
            }
165
166 2
            return array();
167
        }
168
169 15
        switch (count($type['params'])) {
170 15
            case 0:
171
                throw new RuntimeException(sprintf('The array type must be specified either as "array<T>", or "array<K,V>".'));
172
173 15
            case 1:
174 15
                $result = array();
175
176 15
                if (null === $this->result) {
177 5
                    $this->result = &$result;
178 5
                }
179
180 15
                foreach ($nodes as $v) {
181 15
                    $result[] = $this->navigator->accept($v, $type['params'][0], $context);
182 15
                }
183
184 15
                return $result;
185
186 4
            case 2:
187 4
                if (null === $this->currentMetadata) {
188
                    throw new RuntimeException('Maps are not supported on top-level without metadata.');
189
                }
190
191 4
                list($keyType, $entryType) = $type['params'];
192 4
                $result = array();
193 4
                if (null === $this->result) {
194
                    $this->result = &$result;
195
                }
196
197 4
                $nodes = $data->children($namespace)->$entryName;
198 4
                foreach ($nodes as $v) {
199 4
                    $attrs = $v->attributes();
200 4
                    if ( ! isset($attrs[$this->currentMetadata->xmlKeyAttribute])) {
201
                        throw new RuntimeException(sprintf('The key attribute "%s" must be set for each entry of the map.', $this->currentMetadata->xmlKeyAttribute));
202
                    }
203
204 4
                    $k = $this->navigator->accept($attrs[$this->currentMetadata->xmlKeyAttribute], $keyType, $context);
205 4
                    $result[$k] = $this->navigator->accept($v, $entryType, $context);
206 4
                }
207
208 4
                return $result;
209
210
            default:
211
                throw new LogicException(sprintf('The array type does not support more than 2 parameters, but got %s.', json_encode($type['params'])));
212
        }
213
    }
214
215 27
    public function startVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context)
216
    {
217 27
        $this->setCurrentObject($object);
218 27
        $this->objectMetadataStack->push($metadata);
219 27
        if (null === $this->result) {
220 26
            $this->result = $this->currentObject;
221 26
        }
222 27
    }
223
224 24
    public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
225
    {
226 24
        $name = $this->namingStrategy->translateName($metadata);
227
228 24
        if ( ! $metadata->type) {
229
            throw new RuntimeException(sprintf('You must define a type for %s::$%s.', $metadata->reflection->class, $metadata->name));
230
        }
231
232 24
       if ($metadata->xmlAttribute) {
233
234 5
            $attributes = $data->attributes($metadata->xmlNamespace);
235 5
            if (isset($attributes[$name])) {
236 5
                $v = $this->navigator->accept($attributes[$name], $metadata->type, $context);
237 5
                $metadata->setValue($this->currentObject, $v);
238 5
            }
239
240 5
            return;
241
        }
242
243 24
        if ($metadata->xmlValue) {
244 6
            $v = $this->navigator->accept($data, $metadata->type, $context);
245 6
            $metadata->setValue($this->currentObject, $v);
246
247 6
            return;
248
        }
249
250 22
        if ($metadata->xmlCollection) {
251 6
            $enclosingElem = $data;
252 6
            if (!$metadata->xmlCollectionInline) {
253 6
                $enclosingElem = $data->children($metadata->xmlNamespace)->$name;
254 6
            }
255
256 6
            $this->setCurrentMetadata($metadata);
257 6
            $v = $this->navigator->accept($enclosingElem, $metadata->type, $context);
258 6
            $this->revertCurrentMetadata();
259 6
            $metadata->setValue($this->currentObject, $v);
260
261 6
            return;
262
        }
263
264 21
        if ($metadata->xmlNamespace) {
265 4
            $node = $data->children($metadata->xmlNamespace)->$name;
266 4
            if (!$node->count()) {
267 1
                return;
268
            }
269 3
        } else {
270
271 18
            $namespaces = $data->getDocNamespaces();
272
273 18
            if (isset($namespaces[''])) {
274 1
                $prefix = uniqid('ns-');
275 1
                $data->registerXPathNamespace($prefix, $namespaces['']);
276 1
                $nodes = $data->xpath('./'.$prefix. ':'.$name );
277 1
            } else {
278 17
                $nodes = $data->xpath('./'. $name );
279
            }
280 18
            if (empty($nodes)) {
281 1
                return;
282
            }
283 18
            $node = reset($nodes);
284
        }
285
286 20
        $v = $this->navigator->accept($node, $metadata->type, $context);
287
288 20
        $metadata->setValue($this->currentObject, $v);
289 20
    }
290
291 27
    public function endVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
292
    {
293 27
        $rs = $this->currentObject;
294 27
        $this->objectMetadataStack->pop();
295 27
        $this->revertCurrentObject();
296
297 27
        return $rs;
298
    }
299
300 27
    public function setCurrentObject($object)
301
    {
302 27
        $this->objectStack->push($this->currentObject);
303 27
        $this->currentObject = $object;
304 27
    }
305
306
    public function getCurrentObject()
307
    {
308
        return $this->currentObject;
309
    }
310
311 27
    public function revertCurrentObject()
312
    {
313 27
        return $this->currentObject = $this->objectStack->pop();
314
    }
315
316 6
    public function setCurrentMetadata(PropertyMetadata $metadata)
317
    {
318 6
        $this->metadataStack->push($this->currentMetadata);
319 6
        $this->currentMetadata = $metadata;
320 6
    }
321
322
    public function getCurrentMetadata()
323
    {
324
        return $this->currentMetadata;
325
    }
326
327 6
    public function revertCurrentMetadata()
328
    {
329 6
        return $this->currentMetadata = $this->metadataStack->pop();
330
    }
331
332 54
    public function getResult()
333
    {
334 54
        return $this->result;
335
    }
336
337
    /**
338
     * @param array<string> $doctypeWhitelist
339
     */
340 1
    public function setDoctypeWhitelist(array $doctypeWhitelist)
341
    {
342 1
        $this->doctypeWhitelist = $doctypeWhitelist;
343 1
    }
344
345
    /**
346
     * @return array<string>
347
     */
348
    public function getDoctypeWhitelist()
349
    {
350
        return $this->doctypeWhitelist;
351
    }
352
353
    /**
354
     * Retrieves internalSubset even in bugfixed php versions
355
     *
356
     * @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...
357
     * @param string $data
358
     * @return string
359
     */
360 3
    private function getDomDocumentTypeEntitySubset($data)
361
    {
362 3
        $startPos = $endPos = stripos($data, '<!doctype');
363 3
        $braces = 0;
364
        do {
365 3
            $char = $data[$endPos++];
366 3
            if ($char === '<') {
367 3
                ++$braces;
368 3
            }
369 3
            if ($char === '>') {
370 3
                --$braces;
371 3
            }
372 3
        } while ($braces > 0);
373
374 3
        $internalSubset = substr($data, $startPos, $endPos - $startPos);
375 3
        $internalSubset = str_replace(array("\n", "\r"), '', $internalSubset);
376 3
        $internalSubset = preg_replace('/\s{2,}/', ' ', $internalSubset);
377 3
        $internalSubset = str_replace(array("[ <!", "> ]>"), array('[<!', '>]>'), $internalSubset);
378
379 3
        return $internalSubset;
380
    }
381
}
382