Completed
Pull Request — master (#11)
by Eric
65:27
created

XmlDeserializationVisitor::visitNode()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 5.3846
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 8
eloc 17
nc 12
nop 2
crap 72
1
<?php
2
3
/*
4
 * This file is part of the Ivory Serializer package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\Serializer\Visitor\Xml;
13
14
use Ivory\Serializer\Context\ContextInterface;
15
use Ivory\Serializer\Instantiator\InstantiatorInterface;
16
use Ivory\Serializer\Mapping\PropertyMetadataInterface;
17
use Ivory\Serializer\Mapping\TypeMetadataInterface;
18
use Ivory\Serializer\Mutator\MutatorInterface;
19
use Ivory\Serializer\Visitor\AbstractDeserializationVisitor;
20
21
/**
22
 * @author GeLo <[email protected]>
23
 */
24
class XmlDeserializationVisitor extends AbstractDeserializationVisitor
25
{
26
    /**
27
     * @var string
28
     */
29
    private $entry;
30
31
    /**
32
     * @var string
33
     */
34
    private $entryAttribute;
35
36
    /**
37
     * @param InstantiatorInterface $instantiator
38
     * @param MutatorInterface      $mutator
39
     * @param string                $entry
40
     * @param string                $entryAttribute
41
     */
42
    public function __construct(
43
        InstantiatorInterface $instantiator,
44 1312
        MutatorInterface $mutator,
45
        $entry = 'entry',
46
        $entryAttribute = 'key'
47
    ) {
48
        parent::__construct($instantiator, $mutator);
49
50 1312
        $this->entry = $entry;
51
        $this->entryAttribute = $entryAttribute;
52 1312
    }
53 1312
54 1312
    /**
55
     * {@inheritdoc}
56
     */
57
    protected function decode($data)
58
    {
59 132
        $internalErrors = libxml_use_internal_errors();
60
        $disableEntityLoader = libxml_disable_entity_loader();
61 132
62 132
        $this->setLibXmlState(true, true);
63
        $document = simplexml_load_string($data);
64 132
65
        if ($document === false) {
66 132
            $errors = [];
67 132
68 View Code Duplication
            foreach (libxml_get_errors() as $error) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69 132
                $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
70
                    $error->level === LIBXML_ERR_WARNING ? 'WARNING' : 'ERROR',
71
                    $error->code,
72
                    trim($error->message),
73 132
                    $error->file ?: 'n/a',
74
                    $error->line,
75
                    $error->column
76
                );
77
            }
78
79 8
            $this->setLibXmlState($internalErrors, $disableEntityLoader);
80
81 8
            throw new \InvalidArgumentException(implode(PHP_EOL, $errors));
82
        }
83 8
84 8
        $this->setLibXmlState($internalErrors, $disableEntityLoader);
85 8
86
        return $document;
87 8
    }
88 8
89 4
    /**
90 4
     * {@inheritdoc}
91
     */
92 8
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
93 8
    {
94 8
        $this->result = [];
95 4
96 4
        $entry = $this->entry;
97
        $entryAttribute = $this->entryAttribute;
98 8
        $keyAsAttribute = false;
99
        $inline = false;
100
101
        $metadataStack = $context->getMetadataStack();
102
        $metadataIndex = count($metadataStack) - 2;
103
        $metadata = isset($metadataStack[$metadataIndex]) ? $metadataStack[$metadataIndex] : null;
104 96
105 View Code Duplication
        if ($metadata instanceof PropertyMetadataInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
106
            $inline = $metadata->isXmlInline();
107
108
            if ($metadata->hasXmlKeyAsAttribute()) {
109
                $keyAsAttribute = $metadata->useXmlKeyAsAttribute();
110 96
            }
111
112 96
            if ($metadata->hasXmlEntry()) {
113 8
                $entry = $metadata->getXmlEntry();
114
            }
115
116 96
            if ($metadata->hasXmlEntryAttribute()) {
117
                $entryAttribute = $metadata->getXmlEntryAttribute();
118 96
            }
119
        }
120
121
        $keyType = $type->getOption('key');
122
        $valueType = $type->getOption('value');
123
124
        if ($data instanceof \SimpleXMLElement && !$inline) {
125
            $data = $data->children();
126
        }
127 8
128
        foreach ($data as $key => $value) {
129 8
            $result = $value;
130 8
131 4
            if ($value instanceof \SimpleXMLElement && $valueType === null) {
132
                $result = $this->visitNode($value, $entry);
133 4
            }
134
135 8
            $result = $this->navigator->navigate($result, $context, $valueType);
136
137
            if ($result === null && $context->isNullIgnored()) {
138
                continue;
139 8
            }
140 8
141
            if ($key === $entry) {
142 8
                $key = null;
143 4
            }
144 6
145 2
            if ($value instanceof \SimpleXMLElement && ($keyAsAttribute || $key === null)) {
146 8
                $attributes = $value->attributes();
147
                $key = isset($attributes[$entryAttribute]) ? (string) $attributes[$entryAttribute] : null;
148
            }
149
150 8
            $key = $this->navigator->navigate($key, $context, $keyType);
151 8
152 4
            if ($key === null) {
153
                $this->result[] = $result;
154 4
            } else {
155
                $this->result[$key] = $result;
156 8
            }
157
        }
158
159 8
        return $this->result;
160
    }
161 8
162
    /**
163
     * {@inheritdoc}
164
     */
165
    protected function doVisitObjectProperty(
166
        $data,
167
        $name,
168
        PropertyMetadataInterface $property,
169 100
        ContextInterface $context
170
    ) {
171 100
        // FIXME - Impossible to support this behavior...
172 72
        if ($property->isXmlInline() && $property->useXmlKeyAsNode()) {
173
            return false;
174
        }
175 100
176 100
        if ($property->isXmlAttribute()) {
177
            return parent::doVisitObjectProperty($data->attributes(), $name, $property, $context);
178
        }
179 44
180
        if ($property->isXmlValue()) {
181 44
            return parent::doVisitObjectProperty([$name => (string) $data], $name, $property, $context);
182 8
        }
183
184
        $key = $name;
185 40
186 12
        if ($property->isXmlInline()) {
187
            $key = $property->hasXmlEntry() ? $property->getXmlEntry() : $this->entry;
188 32
        }
189
190
        if (!isset($data->$key)) {
191
            return false;
192
        }
193
194
        $data = $data->$key;
195
196
        if ($data->count() <= 1 && (string) $data === '') {
197
            $data = null;
198
        }
199
200
        return parent::doVisitObjectProperty([$name => $data], $name, $property, $context);
201
    }
202
203
    /**
204
     * @param \SimpleXMLElement $data
205
     * @param string|null       $entry
206
     *
207
     * @return mixed
208
     */
209
    private function visitNode(\SimpleXMLElement $data, $entry = null)
210
    {
211
        if ($data->count() === 0) {
212
            $data = (string) $data;
213
214
            return $data !== '' ? $data : null;
215
        }
216
217
        $result = [];
218
        $entry = $entry ?: $this->entry;
219
220
        foreach ($data as $value) {
221
            $key = $value->getName();
222
223
            if ($key === $entry) {
224
                $result[] = $value;
225
            } elseif (isset($result[$key])) {
226
                if (!is_array($result[$key])) {
227
                    $result[$key] = [$result[$key]];
228
                }
229
230
                $result[$key][] = $value;
231
            } else {
232
                $result[$key] = $value;
233
            }
234
        }
235
236
        return $result;
237
    }
238
239
    /**
240
     * @param bool $internalErrors
241
     * @param bool $disableEntities
242
     */
243
    private function setLibXmlState($internalErrors, $disableEntities)
244
    {
245
        libxml_use_internal_errors($internalErrors);
246
        libxml_disable_entity_loader($disableEntities);
247
        libxml_clear_errors();
248
    }
249
}
250