XmlSerializationVisitor::leaveNodeScope()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
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\Accessor\AccessorInterface;
15
use Ivory\Serializer\Context\ContextInterface;
16
use Ivory\Serializer\Mapping\ClassMetadataInterface;
17
use Ivory\Serializer\Mapping\PropertyMetadataInterface;
18
use Ivory\Serializer\Mapping\TypeMetadataInterface;
19
use Ivory\Serializer\Visitor\AbstractVisitor;
20
21
/**
22
 * @author GeLo <[email protected]>
23
 */
24
class XmlSerializationVisitor extends AbstractVisitor
25
{
26
    /**
27
     * @var AccessorInterface
28
     */
29
    private $accessor;
30
31
    /**
32
     * @var \DOMDocument|null
33
     */
34
    private $document;
35
36
    /**
37
     * @var \DOMElement|null
38
     */
39
    private $node;
40
41
    /**
42
     * @var \DOMElement[]
43
     */
44
    private $stack;
45
46
    /**
47
     * @var string
48
     */
49
    private $version;
50
51
    /**
52
     * @var string
53
     */
54
    private $encoding;
55
56
    /**
57
     * @var bool
58
     */
59
    private $formatOutput;
60
61
    /**
62
     * @var string
63
     */
64
    private $root;
65
66
    /**
67
     * @var string
68
     */
69
    private $entry;
70
71
    /**
72
     * @var string
73
     */
74
    private $entryAttribute;
75
76
    /**
77
     * @param AccessorInterface $accessor
78
     * @param string            $version
79
     * @param string            $encoding
80
     * @param bool              $formatOutput
81
     * @param string            $root
82
     * @param string            $entry
83
     * @param string            $entryAttribute
84
     */
85 1492
    public function __construct(
86
        AccessorInterface $accessor,
87
        $version = '1.0',
88
        $encoding = 'UTF-8',
89
        $formatOutput = true,
90
        $root = 'result',
91
        $entry = 'entry',
92
        $entryAttribute = 'key'
93
    ) {
94 1492
        $this->accessor = $accessor;
95 1492
        $this->version = $version;
96 1492
        $this->encoding = $encoding;
97 1492
        $this->formatOutput = $formatOutput;
98 1492
        $this->root = $root;
99 1492
        $this->entry = $entry;
100 1492
        $this->entryAttribute = $entryAttribute;
101 1492
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 220
    public function prepare($data, ContextInterface $context)
107
    {
108 220
        $this->document = null;
109 220
        $this->node = null;
110 220
        $this->stack = [];
111
112 220
        return parent::prepare($data, $context);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 12
    public function visitBoolean($data, TypeMetadataInterface $type, ContextInterface $context)
119
    {
120 12
        return $this->visitText($data ? 'true' : 'false');
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 72
    public function visitData($data, TypeMetadataInterface $type, ContextInterface $context)
127
    {
128 72
        return $this->visitText($data);
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 12 View Code Duplication
    public function visitFloat($data, TypeMetadataInterface $type, ContextInterface $context)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
135
    {
136 12
        $data = (string) $data;
137
138 12
        if (strpos($data, '.') === false) {
139 12
            $data .= '.0';
140
        }
141
142 12
        return $this->visitText($data);
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 148
    public function visitString($data, TypeMetadataInterface $type, ContextInterface $context)
149
    {
150 148
        $document = $this->getDocument();
151 148
        $data = (string) $data;
152
153 148
        $node = strpos($data, '<') !== false || strpos($data, '>') !== false || strpos($data, '&') !== false
154 8
            ? $document->createCDATASection($data)
155 148
            : $document->createTextNode($data);
156
157 148
        return $this->visitNode($node);
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163 168
    public function startVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context)
164
    {
165 168
        $result = parent::startVisitingObject($data, $class, $context);
166
167 168
        if ($result && $class->hasXmlRoot()) {
168 8
            $this->getDocument($class->getXmlRoot());
169
        }
170
171 168
        return $result;
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 220
    public function getResult()
178
    {
179 220
        $document = $this->getDocument();
180
181 220
        if ($document->formatOutput) {
182 220
            $document->loadXML($document->saveXML());
183
        }
184
185 220
        return $document->saveXML();
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 168
    protected function doVisitObjectProperty(
192
        $data,
193
        $name,
194
        PropertyMetadataInterface $property,
195
        ContextInterface $context
196
    ) {
197 168
        if (!$property->isReadable()) {
198 8
            return false;
199
        }
200
201 168
        $value = $this->accessor->getValue(
202 168
            $data,
203 168
            $property->hasAccessor() ? $property->getAccessor() : $property->getName()
204
        );
205
206 168
        if ($value === null && $context->isNullIgnored()) {
207 8
            return false;
208
        }
209
210 164
        $node = $this->createNode($name);
211 164
        $this->enterNodeScope($node);
212 164
        $this->navigator->navigate($value, $context, $property->getType());
213 164
        $this->leaveNodeScope();
214
215 164
        if ($property->isXmlAttribute()) {
216 8
            $this->node->setAttribute($name, $node->nodeValue);
217 164
        } elseif ($property->isXmlValue()) {
218 4
            $this->visitNode($node->firstChild);
219 160
        } elseif ($property->isXmlInline()) {
220 4
            $children = $node->childNodes;
221 4
            $count = $children->length;
222
223 4
            for ($index = 0; $index < $count; ++$index) {
224 4
                $this->visitNode($children->item(0));
225
            }
226
        } else {
227 160
            $this->visitNode($node);
228
        }
229
230 164
        return true;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236 56
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
237
    {
238 56
        $entry = $this->entry;
239 56
        $entryAttribute = $this->entryAttribute;
240 56
        $keyAsAttribute = false;
241 56
        $keyAsNode = true;
242
243 56
        $metadataStack = $context->getMetadataStack();
244 56
        $metadataIndex = count($metadataStack) - 2;
245 56
        $metadata = isset($metadataStack[$metadataIndex]) ? $metadataStack[$metadataIndex] : null;
246
247 56 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...
248 28
            if ($metadata->hasXmlEntry()) {
249 4
                $entry = $metadata->getXmlEntry();
250
            }
251
252 28
            if ($metadata->hasXmlEntryAttribute()) {
253 4
                $entryAttribute = $metadata->getXmlEntryAttribute();
254
            }
255
256 28
            if ($metadata->hasXmlKeyAsAttribute()) {
257 4
                $keyAsAttribute = $metadata->useXmlKeyAsAttribute();
258
            }
259
260 28
            if ($metadata->hasXmlKeyAsNode()) {
261 4
                $keyAsNode = $metadata->useXmlKeyAsNode();
262
            }
263
        }
264
265 56
        $valueType = $type->getOption('value');
266 56
        $ignoreNull = $context->isNullIgnored();
267
268 56
        $this->getDocument();
269
270 56
        foreach ($data as $key => $value) {
271 40
            if ($value === null && $ignoreNull) {
272 4
                continue;
273
            }
274
275 36
            $node = $this->createNode($keyAsNode ? $key : $entry, $entry, $entryAttribute);
276
277 36
            if ($keyAsAttribute) {
278 4
                $node->setAttribute($entryAttribute, $key);
279
            }
280
281 36
            $this->enterNodeScope($node);
282 36
            $this->navigator->navigate($value, $context, $valueType);
283 36
            $this->leaveNodeScope();
284 36
            $this->visitNode($node);
285
        }
286 56
    }
287
288
    /**
289
     * {@inheritdoc}
290
     */
291 80
    private function visitText($data)
292
    {
293 80
        return $this->visitNode($this->getDocument()->createTextNode((string) $data));
294
    }
295
296
    /**
297
     * @param \DOMNode $node
298
     *
299
     * @return \DOMNode
300
     */
301 208
    private function visitNode(\DOMNode $node)
302
    {
303 208
        if ($this->node !== $node) {
304 208
            $this->node->appendChild($node);
305
        }
306
307 208
        return $node;
308
    }
309
310
    /**
311
     * @param \DOMElement $node
312
     */
313 184
    private function enterNodeScope(\DOMElement $node)
314
    {
315 184
        $this->stack[] = $this->node;
316 184
        $this->node = $node;
317 184
    }
318
319 184
    private function leaveNodeScope()
320
    {
321 184
        $this->node = array_pop($this->stack);
322 184
    }
323
324
    /**
325
     * @param string|null $root
326
     *
327
     * @return \DOMDocument
328
     */
329 220
    private function getDocument($root = null)
330
    {
331 220
        return $this->document !== null ? $this->document : $this->document = $this->createDocument($root);
332
    }
333
334
    /**
335
     * @param string      $name
336
     * @param string|null $entry
337
     * @param string|null $entryAttribute
338
     *
339
     * @return \DOMElement
340
     */
341 184
    private function createNode($name, $entry = null, $entryAttribute = null)
342
    {
343 184
        $document = $this->getDocument();
344
345
        try {
346 184
            $node = $document->createElement($name);
347 24
        } catch (\DOMException $e) {
348 24
            $node = $document->createElement($entry ?: $this->entry);
349
350 24
            if (is_string($name)) {
351 8
                $node->setAttribute($entryAttribute ?: $this->entryAttribute, $name);
352
            }
353
        }
354
355 184
        return $node;
356
    }
357
358
    /**
359
     * @param string|null $root
360
     *
361
     * @return \DOMDocument
362
     */
363 220
    private function createDocument($root = null)
364
    {
365 220
        $document = new \DOMDocument($this->version, $this->encoding);
366 220
        $document->formatOutput = $this->formatOutput;
367
368 220
        $this->node = $document->createElement($root ?: $this->root);
369 220
        $document->appendChild($this->node);
370
371 220
        return $document;
372
    }
373
}
374