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

XmlSerializationVisitor::doVisitObjectProperty()   D

Complexity

Conditions 9
Paths 6

Size

Total Lines 41
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 0
loc 41
rs 4.909
c 2
b 1
f 0
ccs 22
cts 22
cp 1
cc 9
eloc 28
nc 6
nop 4
crap 9
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 1312
     * @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
    public function __construct(
86 1312
        AccessorInterface $accessor,
87 1312
        $version = '1.0',
88 1312
        $encoding = 'UTF-8',
89 1312
        $formatOutput = true,
90 1312
        $root = 'result',
91 1312
        $entry = 'entry',
92 1312
        $entryAttribute = 'key'
93
    ) {
94
        $this->accessor = $accessor;
95
        $this->version = $version;
96
        $this->encoding = $encoding;
97 196
        $this->formatOutput = $formatOutput;
98
        $this->root = $root;
99 196
        $this->entry = $entry;
100 196
        $this->entryAttribute = $entryAttribute;
101 196
    }
102
103 196
    /**
104
     * {@inheritdoc}
105
     */
106
    public function prepare($data, ContextInterface $context)
107
    {
108
        $this->document = null;
109 12
        $this->node = null;
110
        $this->stack = [];
111 12
112
        return parent::prepare($data, $context);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117 64
     */
118
    public function visitBoolean($data, TypeMetadataInterface $type, ContextInterface $context)
119 64
    {
120
        return $this->visitText($data ? 'true' : 'false');
121
    }
122
123
    /**
124
     * {@inheritdoc}
125 12
     */
126
    public function visitData($data, TypeMetadataInterface $type, ContextInterface $context)
127 12
    {
128
        return $this->visitText($data);
129 12
    }
130 12
131 6
    /**
132
     * {@inheritdoc}
133 12
     */
134 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
        $data = (string) $data;
137
138
        if (strpos($data, '.') === false) {
139 124
            $data .= '.0';
140
        }
141 124
142 124
        return $this->visitText($data);
143
    }
144 124
145 62
    /**
146 124
     * {@inheritdoc}
147
     */
148 124
    public function visitString($data, TypeMetadataInterface $type, ContextInterface $context)
149
    {
150
        $document = $this->getDocument();
151
        $data = (string) $data;
152
153
        $node = strpos($data, '<') !== false || strpos($data, '>') !== false || strpos($data, '&') !== false
154 196
            ? $document->createCDATASection($data)
155
            : $document->createTextNode($data);
156 196
157
        return $this->visitNode($node);
158 196
    }
159 196
160 98
    /**
161
     * {@inheritdoc}
162 196
     */
163
    public function startVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context)
164
    {
165
        $result = parent::startVisitingObject($data, $class, $context);
166
167
        if ($result && $class->hasXmlRoot()) {
168 160
            $this->getDocument($class->getXmlRoot());
169
        }
170
171
        return $result;
172
    }
173
174 160
    /**
175 8
     * {@inheritdoc}
176
     */
177
    public function getResult()
178
    {
179 160
        $document = $this->getDocument();
180 80
181 160
        if ($document->formatOutput) {
182 80
            $document->loadXML($document->saveXML());
183
        }
184 160
185 8
        return $document->saveXML();
186
    }
187
188 156
    /**
189 156
     * {@inheritdoc}
190 156
     */
191 156
    protected function doVisitObjectProperty(
192 156
        $data,
193
        $name,
194 156
        PropertyMetadataInterface $property,
195
        ContextInterface $context
196
    ) {
197
        if (!$property->isReadable()) {
198
            return false;
199
        }
200 36
201
        $value = $this->accessor->getValue(
202 36
            $data,
203 36
            $property->hasAccessor() ? $property->getAccessor() : $property->getName()
204
        );
205 36
206
        if ($value === null && $context->isNullIgnored()) {
207 36
            return false;
208 20
        }
209 4
210
        $node = $this->createNode($name);
211
        $this->enterNodeScope($node);
212 16
        $this->navigator->navigate($value, $context, $property->getType());
213
        $this->leaveNodeScope();
214 16
215 16
        if ($property->isXmlAttribute()) {
216 8
            $this->node->setAttribute($name, $node->nodeValue);
217
        } elseif ($property->isXmlValue()) {
218 16
            $this->visitNode($node->firstChild);
219 16
        } elseif ($property->isXmlInline()) {
220 16
            $children = $node->childNodes;
221 16
            $count = $children->length;
222 18
223 36
            for ($index = 0; $index < $count; ++$index) {
224
                $this->visitNode($children->item(0));
225
            }
226
        } else {
227
            $this->visitNode($node);
228 72
        }
229
230 72
        return true;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
237
    {
238 188
        $entry = $this->entry;
239
        $entryAttribute = $this->entryAttribute;
240 188
        $keyAsAttribute = false;
241 188
        $keyAsNode = true;
242 94
243
        $metadataStack = $context->getMetadataStack();
244 188
        $metadataIndex = count($metadataStack) - 2;
245
        $metadata = isset($metadataStack[$metadataIndex]) ? $metadataStack[$metadataIndex] : null;
246
247 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
            if ($metadata->hasXmlKeyAsAttribute()) {
249
                $keyAsAttribute = $metadata->useXmlKeyAsAttribute();
250 160
            }
251
252 160
            if ($metadata->hasXmlKeyAsNode()) {
253 160
                $keyAsNode = $metadata->useXmlKeyAsNode();
254 160
            }
255
256 160
            if ($metadata->hasXmlEntry()) {
257
                $entry = $metadata->getXmlEntry();
258 160
            }
259 160
260
            if ($metadata->hasXmlEntryAttribute()) {
261
                $entryAttribute = $metadata->getXmlEntryAttribute();
262
            }
263
        }
264
265
        $valueType = $type->getOption('value');
266 124
        $ignoreNull = $context->isNullIgnored();
267
268 124
        $this->getDocument();
269
270
        foreach ($data as $key => $value) {
271
            if ($value === null && $ignoreNull) {
272
                continue;
273
            }
274 196
275
            $node = $this->createNode($keyAsNode ? $key : $entry, $entry, $entryAttribute);
276 196
277
            if ($keyAsAttribute) {
278
                $node->setAttribute($entryAttribute, $key);
279
            }
280
281
            $this->enterNodeScope($node);
282
            $this->navigator->navigate($value, $context, $valueType);
283
            $this->leaveNodeScope();
284 160
            $this->visitNode($node);
285
        }
286 160
    }
287
288
    /**
289 160
     * {@inheritdoc}
290 82
     */
291 4
    private function visitText($data)
292 4
    {
293
        return $this->visitNode($this->getDocument()->createTextNode((string) $data));
294
    }
295 160
296
    /**
297
     * @param \DOMNode $node
298
     *
299
     * @return \DOMNode
300
     */
301 196
    private function visitNode(\DOMNode $node)
302
    {
303 196
        if ($this->node !== $node) {
304 196
            $this->node->appendChild($node);
305
        }
306 196
307 196
        return $node;
308
    }
309 196
310
    /**
311
     * @param \DOMElement $node
312
     */
313
    private function enterNodeScope(\DOMElement $node)
314
    {
315
        $this->stack[] = $this->node;
316
        $this->node = $node;
317
    }
318
319
    private function leaveNodeScope()
320
    {
321
        $this->node = array_pop($this->stack);
322
    }
323
324
    /**
325
     * @param string|null $root
326
     *
327
     * @return \DOMDocument
328
     */
329
    private function getDocument($root = null)
330
    {
331
        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
    private function createNode($name, $entry = null, $entryAttribute = null)
342
    {
343
        $document = $this->getDocument();
344
345
        try {
346
            $node = $document->createElement($name);
347
        } catch (\DOMException $e) {
348
            $node = $document->createElement($entry ?: $this->entry);
349
            $node->setAttribute($entryAttribute ?: $this->entryAttribute, $name);
350
        }
351
352
        return $node;
353
    }
354
355
    /**
356
     * @param string|null $root
357
     *
358
     * @return \DOMDocument
359
     */
360
    private function createDocument($root = null)
361
    {
362
        $document = new \DOMDocument($this->version, $this->encoding);
363
        $document->formatOutput = $this->formatOutput;
364
365
        $this->node = $document->createElement($root ?: $this->root);
366
        $document->appendChild($this->node);
367
368
        return $document;
369
    }
370
}
371