Passed
Push — master ( b1e342...457070 )
by Dominik
02:01
created

XmlTransformer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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