XmlDeserializationVisitor::doVisitArray()   F
last analyzed

Complexity

Conditions 19
Paths 972

Size

Total Lines 70

Duplication

Lines 15
Ratio 21.43 %

Code Coverage

Tests 38
CRAP Score 19.006

Importance

Changes 0
Metric Value
dl 15
loc 70
c 0
b 0
f 0
ccs 38
cts 39
cp 0.9744
rs 0.3888
cc 19
nc 972
nop 3
crap 19.006

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
 * 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 1492
    public function __construct(
43
        InstantiatorInterface $instantiator,
44
        MutatorInterface $mutator,
45
        $entry = 'entry',
46
        $entryAttribute = 'key'
47
    ) {
48 1492
        parent::__construct($instantiator, $mutator);
49
50 1492
        $this->entry = $entry;
51 1492
        $this->entryAttribute = $entryAttribute;
52 1492
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57 144
    protected function decode($data)
58
    {
59 144
        $internalErrors = libxml_use_internal_errors();
60 144
        $disableEntityLoader = libxml_disable_entity_loader();
61
62 144
        $this->setLibXmlState(true, true);
63 144
        $document = simplexml_load_string($data);
64
65 144
        if ($document === false) {
66
            $errors = [];
67
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
                $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
                    $error->file ?: 'n/a',
74
                    $error->line,
75
                    $error->column
76
                );
77
            }
78
79
            $this->setLibXmlState($internalErrors, $disableEntityLoader);
80
81
            throw new \InvalidArgumentException(implode(PHP_EOL, $errors));
82
        }
83
84 144
        $this->setLibXmlState($internalErrors, $disableEntityLoader);
85
86 144
        return $document;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 12
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
93
    {
94 12
        $this->result = [];
95
96 12
        $entry = $this->entry;
97 12
        $entryAttribute = $this->entryAttribute;
98 12
        $keyAsAttribute = false;
99 12
        $inline = false;
100
101 12
        $metadataStack = $context->getMetadataStack();
102 12
        $metadataIndex = count($metadataStack) - 2;
103 12
        $metadata = isset($metadataStack[$metadataIndex]) ? $metadataStack[$metadataIndex] : null;
104
105 12 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 8
            $inline = $metadata->isXmlInline();
107
108 8
            if ($metadata->hasXmlEntry()) {
109 4
                $entry = $metadata->getXmlEntry();
110
            }
111
112 8
            if ($metadata->hasXmlEntryAttribute()) {
113 4
                $entryAttribute = $metadata->getXmlEntryAttribute();
114
            }
115
116 8
            if ($metadata->hasXmlKeyAsAttribute()) {
117 4
                $keyAsAttribute = $metadata->useXmlKeyAsAttribute();
118
            }
119
        }
120
121 12
        $keyType = $type->getOption('key');
122 12
        $valueType = $type->getOption('value');
123
124 12
        if ($data instanceof \SimpleXMLElement && !$inline) {
125 12
            $data = $data->children();
126
        }
127
128 12
        foreach ($data as $key => $value) {
129 12
            $result = $value;
130 12
            $isElement = $value instanceof \SimpleXMLElement;
131
132 12
            if ($isElement && $valueType === null) {
133 8
                $result = $this->visitNode($value, $entry);
134
            }
135
136 12
            $result = $this->navigator->navigate($result, $context, $valueType);
137
138 12
            if ($result === null && $context->isNullIgnored()) {
139
                continue;
140
            }
141
142 12
            if ($key === $entry) {
143 8
                $key = null;
144
            }
145
146 12
            if ($isElement && ($keyAsAttribute || $key === null)) {
147 8
                $attributes = $value->attributes();
148 8
                $key = isset($attributes[$entryAttribute]) ? $this->visitNode($attributes[$entryAttribute]) : null;
149
            }
150
151 12
            $key = $this->navigator->navigate($key, $context, $keyType);
152
153 12
            if ($key === null) {
154 8
                $this->result[] = $result;
155
            } else {
156 12
                $this->result[$key] = $result;
157
            }
158
        }
159
160 12
        return $this->result;
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 104
    protected function doVisitObjectProperty(
167
        $data,
168
        $name,
169
        PropertyMetadataInterface $property,
170
        ContextInterface $context
171
    ) {
172 104
        if ($property->isXmlInline() && $property->useXmlKeyAsNode()) {
173
            return false;
174
        }
175
176 104
        if ($property->isXmlAttribute()) {
177 8
            return parent::doVisitObjectProperty($data->attributes(), $name, $property, $context);
178
        }
179
180 104
        if ($property->isXmlValue()) {
181 4
            return parent::doVisitObjectProperty([$name => $data], $name, $property, $context);
182
        }
183
184 100
        $key = $name;
185
186 100
        if ($property->isXmlInline()) {
187 4
            $key = $property->hasXmlEntry() ? $property->getXmlEntry() : $this->entry;
188
        }
189
190 100
        if (!isset($data->$key)) {
191 8
            return false;
192
        }
193
194 100
        $data = $data->$key;
195
196 100
        if ($data->count() === 1 && (string) $data === '') {
197 40
            return false;
198
        }
199
200 72
        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 8
    private function visitNode(\SimpleXMLElement $data, $entry = null)
210
    {
211 8
        if ($data->count() === 0) {
212 8
            $data = (string) $data;
213
214 8
            return $data !== '' ? $data : null;
215
        }
216
217 4
        $result = [];
218 4
        $entry = $entry ?: $this->entry;
219
220 4
        foreach ($data as $value) {
221 4
            $key = $value->getName();
222
223 4
            if ($key === $entry) {
224 4
                $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 2
                $result[$key] = $value;
233
            }
234
        }
235
236 4
        return $result;
237
    }
238
239
    /**
240
     * @param bool $internalErrors
241
     * @param bool $disableEntities
242
     */
243 144
    private function setLibXmlState($internalErrors, $disableEntities)
244
    {
245 144
        libxml_use_internal_errors($internalErrors);
246 144
        libxml_disable_entity_loader($disableEntities);
247 144
        libxml_clear_errors();
248 144
    }
249
}
250