Completed
Pull Request — master (#11)
by Eric
04:56
created

XmlSerializationVisitor::doVisitObjectProperty()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 34
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 0
loc 34
ccs 20
cts 20
cp 1
rs 6.7272
c 2
b 1
f 0
cc 7
eloc 23
nc 5
nop 4
crap 7
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 1376
    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 1376
        $this->accessor = $accessor;
95 1376
        $this->version = $version;
96 1376
        $this->encoding = $encoding;
97 1376
        $this->formatOutput = $formatOutput;
98 1376
        $this->root = $root;
99 1376
        $this->entry = $entry;
100 1376
        $this->entryAttribute = $entryAttribute;
101 1376
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 204
    public function prepare($data, ContextInterface $context)
107
    {
108 204
        $this->document = null;
109 204
        $this->node = null;
110 204
        $this->stack = [];
111
112 204
        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 64
    public function visitData($data, TypeMetadataInterface $type, ContextInterface $context)
127
    {
128 64
        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 6
        }
141
142 12
        return $this->visitText($data);
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 132
    public function visitString($data, TypeMetadataInterface $type, ContextInterface $context)
149
    {
150 132
        $document = $this->getDocument();
151 132
        $data = (string) $data;
152
153 132
        $node = $this->requireCData($data)
154 66
            ? $document->createCDATASection($data)
155 132
            : $document->createTextNode($data);
156
157 132
        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 4
        }
170
171 168
        return $result;
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 204
    public function getResult()
178
    {
179 204
        $document = $this->getDocument();
180
181 204
        if ($document->formatOutput) {
182 204
            $document->loadXML($document->saveXML());
183 102
        }
184
185 204
        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 84
            $data,
203 168
            $property->hasAccessor() ? $property->getAccessor() : $property->getName()
204 84
        );
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 2
        } else {
220 160
            $this->visitNode($node);
221
        }
222
223 164
        return true;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 36
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
230
    {
231 36
        $ignoreNull = $context->isNullIgnored();
232 36
        $valueType = $type->getOption('value');
233
234 36
        $this->getDocument();
235
236 36
        foreach ($data as $key => $value) {
237 20
            if ($value === null && $ignoreNull) {
238 4
                continue;
239
            }
240
241 16
            $node = $this->createNode(is_string($key) ? $key : $this->entry);
242
243 16
            if (is_int($key)) {
244 16
                $node->setAttribute($this->entryAttribute, $key);
245 8
            }
246
247 16
            $this->enterNodeScope($node);
248 16
            $this->navigator->navigate($value, $context, $valueType);
249 16
            $this->leaveNodeScope();
250 16
            $this->visitNode($node);
251 18
        }
252 36
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257 72
    private function visitText($data)
258
    {
259 72
        return $this->visitNode($this->getDocument()->createTextNode((string) $data));
260
    }
261
262
    /**
263
     * @param \DOMNode $node
264
     *
265
     * @return \DOMNode
266
     */
267 196
    private function visitNode(\DOMNode $node)
268
    {
269 196
        if ($this->node !== $node) {
270 196
            $this->node->appendChild($node);
271 98
        }
272
273 196
        return $node;
274
    }
275
276
    /**
277
     * @param \DOMElement $node
278
     */
279 168
    private function enterNodeScope(\DOMElement $node)
280
    {
281 168
        $this->stack[] = $this->node;
282 168
        $this->node = $node;
283 168
    }
284
285 168
    private function leaveNodeScope()
286
    {
287 168
        $this->node = array_pop($this->stack);
288 168
    }
289
290
    /**
291
     * @param string $data
292
     *
293
     * @return bool
294
     */
295 132
    private function requireCData($data)
296
    {
297 132
        return strpos($data, '<') !== false || strpos($data, '>') !== false || strpos($data, '&') !== false;
298
    }
299
300
    /**
301
     * @param string|null $root
302
     *
303
     * @return \DOMDocument
304
     */
305 204
    private function getDocument($root = null)
306
    {
307 204
        return $this->document !== null ? $this->document : $this->document = $this->createDocument($root);
308
    }
309
310
    /**
311
     * @param string $name
312
     *
313
     * @return \DOMElement
314
     */
315 168
    private function createNode($name)
316
    {
317 168
        $document = $this->getDocument();
318
319
        try {
320 168
            $element = $document->createElement($name);
321 86
        } catch (\DOMException $e) {
322 4
            $element = $document->createElement($this->entry);
323 4
            $element->setAttribute($this->entryAttribute, $name);
324
        }
325
326 168
        return $element;
327
    }
328
329
    /**
330
     * @param string|null $root
331
     *
332
     * @return \DOMDocument
333
     */
334 204
    private function createDocument($root = null)
335
    {
336 204
        $document = new \DOMDocument($this->version, $this->encoding);
337 204
        $document->formatOutput = $this->formatOutput;
338
339 204
        $this->node = $document->createElement($root ?: $this->root);
340 204
        $document->appendChild($this->node);
341
342 204
        return $document;
343
    }
344
}
345