Completed
Pull Request — master (#11)
by Eric
21:21 queued 18:00
created

XmlSerializationVisitor::prepare()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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