Completed
Push — master ( 05e5ca...89c229 )
by Chris
02:12
created

XmlSerializer::serialize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
rs 9.4285
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
                    if ($node->attributes() && !isset($deserialized_xml[ $node->getName() ]['@attributes'])) {
102
                        if (!is_array($deserialized_xml[ $node->getName() ])) {
103
                            $deserialized_xml[ $node->getName() ] = [$deserialized_xml[ $node->getName() ]];
104
                        }
105
106
                        $attributes = (array)$node->attributes();
107
                        $deserialized_xml[ $node->getName() ]['@attributes'] = $attributes['@attributes'];
108
                    }
109
                } else {
110
                    $deserialized_xml[ $node->getName() ] = self::ensureAttributes($deserialized_xml[ $node->getName() ], $node);
111
                }
112
            }
113
114
            return $deserialized_xml;
115
        }
116
117
        /**
118
         * json encode/decode results in <node/> being converted to [ 'node' => [] ]
119
         * when our expected behaviour is [ 'node' => null ]
120
         *
121
         * @param array $array
122
         *
123
         * @return array
124
         */
125
        private static function fixNullNodes(array $array) {
126
            foreach ($array as $k => $value) {
127
                if (empty($value)) {
128
                    $array[ $k ] = null;
129
                } else if (is_array($value)) {
130
                    $array[ $k ] = self::fixNullNodes($value);
131
                }
132
            }
133
134
            return $array;
135
        }
136
137
        /**
138
         * @param mixed       $data
139
         * @param string|null $parent_node
140
         * @param string      $attributes [optional]
141
         *
142
         * @return string
143
         */
144
        private static function buildXml($data, $parent_node, $attributes = null) {
145
            if (!is_array($data)) {
146
                return self::wrapNode($parent_node, $data, $attributes);
147
            }
148
149
            $result = '';
150
            $attributes = self::getAttributesString(array_get($data, '@attributes', []));
151
            unset($data['@attributes']);
152
153
            foreach ($data as $key => $value) {
154
                if (is_numeric($key)) {
155
                    $result .= self::buildXml($value, null, $attributes);
156
                } else {
157
                    $result .= self::buildXml($value, $key);
158
                }
159
            }
160
161
            return self::wrapNode($parent_node, $result, $attributes);
162
        }
163
164
        /**
165
         * Wrap $content in an xml node
166
         * ie <node attrName="attrVal">content</node>
167
         *
168
         * @param string $name
169
         * @param string $content
170
         * @param string $attributes
171
         *
172
         * @return null|string
173
         */
174
        private static function wrapNode($name, $content = null, $attributes = '') {
175
            if (empty($name)) {
176
                return $content;
177
            }
178
179
            $node = "<$name";
180
            if ($attributes) {
181
                $node .= ' ' . trim($attributes);
182
            }
183
184
            if (empty($content)) {
185
                return $node . '/>';
186
            }
187
188
            return $node . '>' . $content . "</$name>";
189
        }
190
191
        /**
192
         * @param array $attributes
193
         *
194
         * @return null|string
195
         */
196
        private static function getAttributesString(array $attributes) {
197
            if (empty($attributes)) {
198
                return null;
199
            }
200
            $result = '';
201
            foreach ($attributes as $k => $v) {
202
                $result .= $k . '="' . $v . '" ';
203
            }
204
205
206
            return $result;
207
        }
208
    }