Test Failed
Push — master ( 8a2b60...8e4848 )
by Dominik
03:00
created

XmlTypeEncoder::dataToScalarNode()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
c 0
b 0
f 0
rs 8.439
cc 6
eloc 15
nc 8
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chubbyphp\Serialization\Encoder;
6
7
use Chubbyphp\Serialization\SerializerRuntimeException;
8
use Doctrine\Common\Inflector\Inflector;
9
10
final class XmlTypeEncoder implements TypeEncoderInterface
11
{
12
    /**
13
     * @var bool
14
     */
15
    private $prettyPrint;
16
17
    /**
18
     * @param bool $prettyPrint
19
     */
20
    public function __construct(bool $prettyPrint = false)
21
    {
22
        $this->prettyPrint = $prettyPrint;
23
    }
24
25
    /**
26
     * @return string
27
     */
28
    public function getContentType(): string
29
    {
30
        return 'application/xml';
31
    }
32
33
    /**
34
     * @param array $data
35
     *
36
     * @return string
37
     *
38
     * @throws SerializerRuntimeException
39
     */
40
    public function encode(array $data): string
41
    {
42
        if (!isset($data['_type'])) {
43
            throw new \InvalidArgumentException('Data missing _type');
44
        }
45
46
        $document = new \DOMDocument('1.0', 'UTF-8');
47
        $document->formatOutput = $this->prettyPrint;
48
49
        $listNode = $this->createMetadataNode($document, $data['_type']);
50
        $document->appendChild($listNode);
51
52
        unset($data['_type']);
53
54
        $this->dataToNodes($document, $listNode, $data);
55
56
        return trim($document->saveXML(null, LIBXML_NOEMPTYTAG));
57
    }
58
59
    /**
60
     * @param \DOMDocument $document
61
     * @param string       $type
62
     *
63
     * @return \DOMNode
64
     */
65
    private function createMetadataNode(\DOMDocument $document, string $type): \DOMNode
66
    {
67
        $node = $document->createElement('object');
68
        if (null !== $type) {
69
            $node->setAttribute('type', $type);
70
        }
71
72
        return $node;
73
    }
74
75
    /**
76
     * @param string $name
77
     *
78
     * @return string
79
     */
80
    private function getMetaPrefix(string $name)
81
    {
82
        return 'meta-'.$name;
83
    }
84
85
    /**
86
     * @param \DOMDocument $document
87
     * @param \DOMNode     $listNode
88
     * @param array        $data
89
     */
90
    private function dataToNodes(\DOMDocument $document, \DOMNode $listNode, array $data)
91
    {
92
        foreach ($data as $key => $value) {
93
            if (is_string($key) && '_' === $key[0]) {
94
                $key = $this->getMetaPrefix(substr($key, 1));
95
            }
96
97
            if (is_array($value)) {
98
                $childNode = $this->dataToArrayNode($document, $listNode, $key, $value);
99
            } else {
100
                $childNode = $this->dataToScalarNode($document, $listNode, $key, $value);
101
            }
102
103
            if (is_int($key)) {
104
                $childNode->setAttribute('key', (string) $key);
105
            }
106
107
            $listNode->appendChild($childNode);
108
        }
109
    }
110
111
    /**
112
     * @param \DOMDocument $document
113
     * @param \DOMNode     $listNode
114
     * @param string|int   $key
115
     * @param array        $value
116
     *
117
     * @return \DOMElement|\DOMNode
118
     */
119
    private function dataToArrayNode(\DOMDocument $document, \DOMNode $listNode, $key, array $value)
120
    {
121
        if (!isset($value['_type'])) {
122
            $childNode = $document->createElement(is_int($key) ? Inflector::singularize($listNode->nodeName) : $key);
123
            $this->dataToNodes($document, $childNode, $value);
124
125
            return $childNode;
126
        }
127
128
        if (is_int($key)) {
129
            $childNode = $this->createMetadataNode($document, $value['_type']);
130
131
            unset($value['_type']);
132
133
            $this->dataToNodes($document, $childNode, $value);
134
135
            return $childNode;
136
        }
137
138
        $subChildNode = $this->createMetadataNode($document, $value['_type']);
139
140
        unset($value['_type']);
141
142
        $childNode = $document->createElement($key);
143
        $childNode->appendChild($subChildNode);
144
145
        $this->dataToNodes($document, $subChildNode, $value);
146
147
        return $childNode;
148
    }
149
150
    /**
151
     * @param \DOMDocument               $document
152
     * @param \DOMNode                   $listNode
153
     * @param string|int                 $key
154
     * @param string|null|bool|float|int $value
155
     *
156
     * @return \DOMNode
157
     */
158
    private function dataToScalarNode(\DOMDocument $document, \DOMNode $listNode, $key, $value): \DOMNode
159
    {
160
        $stringKey = is_int($key) ? Inflector::singularize($listNode->nodeName) : $key;
161
162
        if (is_string($value)) {
163
            if (false !== strpos($value, '<') || false !== strpos($value, '&')) {
164
                $childNode = $document->createElement($stringKey);
165
                $childNode->appendChild($document->createCDATASection($value));
166
            } else {
167
                $childNode = $document->createElement($stringKey, $value);
168
            }
169
170
            $childNode->setAttribute('type', 'string');
171
172
            return $childNode;
173
        }
174
175
        if (null === $value) {
176
            return $document->createElement($stringKey);
177
        }
178
179
        $childNode = $document->createElement($stringKey, $this->getValueAsString($value));
180
        $childNode->setAttribute('type', $this->getType($value));
181
182
        return $childNode;
183
    }
184
185
    /**
186
     * @param bool|float|int $value
187
     *
188
     * @return string|null
189
     *
190
     * @throws \InvalidArgumentException
191
     */
192
    private function getValueAsString($value): string
193
    {
194
        if (is_bool($value)) {
195
            return $value ? 'true' : 'false';
196
        }
197
198
        if (is_float($value) || is_int($value)) {
199
            return (string) $value;
200
        }
201
202
        throw new \InvalidArgumentException(sprintf('Unsupported data type: %s', gettype($value)));
203
    }
204
205
    /**
206
     * @param bool|float|int|string $value
207
     *
208
     * @return null|string
209
     */
210
    private function getType($value): string
211
    {
212
        if (is_bool($value)) {
213
            return 'boolean';
214
        }
215
216
        if (is_float($value)) {
217
            return 'float';
218
        }
219
220
        return 'integer';
221
    }
222
}
223