Passed
Push — master ( 525cd7...c4562e )
by Chris
01:40
created

XmlSerializer::deserialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
    namespace rAPId\Data\Serialization;
4
5
    use SimpleXMLElement;
6
7
    class XmlSerializer implements Serializer
8
    {
9
        const DEFAULT_ROOT_NODE = 'response';
10
11
        /**
12
         * @param mixed $data
13
         *
14
         * @return mixed|string
15
         */
16
        public static function serialize($data) {
17
            $root_node = self::getRootNode($data);
18
            if (is_object($data)) {
19
                $data = get_object_as_array($data);
20
            }
21
22
            return self::buildXml($data, $root_node);
23
        }
24
25
        /**
26
         * Deserialize a json/xml string to an array
27
         *
28
         * @param string $xml_string
29
         *
30
         * @return mixed
31
         */
32
        public static function deserialize($xml_string) {
33
            $simple_xml = simplexml_load_string($xml_string);
34
35
            return self::simpleXmlToArray($simple_xml);
36
        }
37
38
        /**
39
         * Get the correct HTTP header for the given serialized output
40
         *
41
         * @return string
42
         */
43
        public static function getHttpHeader() {
44
            return 'Content-type: text/xml; charset=utf-8';
45
        }
46
47
        /**
48
         * If $data is an object, uses the class name
49
         * otherwise uses the default
50
         *
51
         * @param mixed $data
52
         *
53
         * @return string $root_node
54
         */
55
        private static function getRootNode($data) {
56
            $root_note = static::DEFAULT_ROOT_NODE;
57
58
            if (is_object($data)) {
59
                $r = new \ReflectionClass($data);
60
                $class_name = $r->getShortName();
61
                $root_note = snake_case($class_name);
62
            }
63
64
            return $root_note;
65
        }
66
67
        /**
68
         * @param SimpleXMLElement $xml
69
         *
70
         * @return array|mixed
71
         */
72
        private static function simpleXmlToArray(SimpleXMLElement $xml) {
73
74
            $array = json_decode(json_encode($xml), true);
75
76
            $array = self::ensureAttributes($array, $xml);
77
            $array = self::fixNullNodes($array);
78
79
            // In the case of <node>76</node>
80
            // just return 76
81
            if (count($array) == 1 && key($array) === 0) {
82
                return $array[0];
83
            }
84
85
            return $array;
86
        }
87
88
        /**
89
         * Calling json encode/decode on SimpleXml can result in missing attributes
90
         * This function recursively checks all simpleXml nodes for attributes and adds them
91
         * to the deserialized xml array
92
         *
93
         * @param array            $deserialized_xml
94
         * @param SimpleXMLElement $simple_xml
95
         *
96
         * @return array
97
         */
98
        private static function ensureAttributes(array $deserialized_xml, SimpleXMLElement $simple_xml) {
99
            foreach ($simple_xml as $node) {
100
                if (count($node->children()) == 0) {
101
                    $attributes = (array)$node->attributes();
102
                    $deserialized_xml = self::addAttributesToXmlArray($deserialized_xml, $node->getName(), $attributes);
103
                } else {
104
                    $deserialized_xml[ $node->getName() ] = self::ensureAttributes($deserialized_xml[ $node->getName() ], $node);
105
                }
106
            }
107
108
            return $deserialized_xml;
109
        }
110
111
        /**
112
         * @param array  $xml
113
         * @param string $name
114
         * @param array  $attributes
115
         *
116
         * @return array $xml
117
         */
118
        private static function addAttributesToXmlArray(array $xml, $name, array $attributes) {
119
            if (!empty($attributes) && !isset($xml[ $name ]['@attributes'])) {
120
                $xml[ $name ] = array_wrap($xml[ $name ]);
121
                $xml[ $name ]['@attributes'] = $attributes['@attributes'];
122
            }
123
124
            return $xml;
125
        }
126
127
        /**
128
         * json encode/decode results in <node/> being converted to [ 'node' => [] ]
129
         * when our expected behaviour is [ 'node' => null ]
130
         *
131
         * @param array $array
132
         *
133
         * @return array
134
         */
135
        private static function fixNullNodes(array $array) {
136
            foreach ($array as $k => $value) {
137
                if (empty($value)) {
138
                    $array[ $k ] = null;
139
                } else if (is_array($value)) {
140
                    $array[ $k ] = self::fixNullNodes($value);
141
                }
142
            }
143
144
            return $array;
145
        }
146
147
        /**
148
         * @param mixed       $data
149
         * @param string|null $parent_node
150
         * @param string      $attributes [optional]
151
         *
152
         * @return string
153
         */
154
        private static function buildXml($data, $parent_node, $attributes = null) {
155
            if (!is_array($data)) {
156
                return self::wrapNode($parent_node, $data, $attributes);
157
            }
158
159
            $result = '';
160
            $attributes = self::getAttributesString(array_get($data, '@attributes', []));
161
            unset($data['@attributes']);
162
163
            foreach ($data as $key => $value) {
164
                if (is_numeric($key)) {
165
                    $result .= self::buildXml($value, null, $attributes);
166
                } else {
167
                    $result .= self::buildXml($value, $key);
168
                }
169
            }
170
171
            return self::wrapNode($parent_node, $result, $attributes);
172
        }
173
174
        /**
175
         * Wrap $content in an xml node
176
         * ie <node attrName="attrVal">content</node>
177
         *
178
         * @param string $name
179
         * @param string $content
180
         * @param string $attributes
181
         *
182
         * @return null|string
183
         */
184
        private static function wrapNode($name, $content = null, $attributes = '') {
185
            if (empty($name)) {
186
                return $content;
187
            }
188
189
            $node = "<$name";
190
            if ($attributes) {
191
                $node .= ' ' . trim($attributes);
192
            }
193
194
            if (empty($content)) {
195
                return $node . '/>';
196
            }
197
198
            return $node . '>' . $content . "</$name>";
199
        }
200
201
        /**
202
         * @param array $attributes
203
         *
204
         * @return null|string
205
         */
206
        private static function getAttributesString(array $attributes) {
207
            if (empty($attributes)) {
208
                return null;
209
            }
210
            $result = '';
211
            foreach ($attributes as $k => $v) {
212
                $result .= $k . '="' . $v . '" ';
213
            }
214
215
216
            return $result;
217
        }
218
    }