Issues (7)

src/Data/Serialization/XmlSerializer.php (2 issues)

Labels
Severity
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);
0 ignored issues
show
It seems like $simple_xml can also be of type false; however, parameter $xml of rAPId\Data\Serialization...zer::simpleXmlToArray() does only seem to accept SimpleXMLElement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

35
            return self::simpleXmlToArray(/** @scrutinizer ignore-type */ $simple_xml);
Loading history...
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 mixed       $data
69
         * @param string|null $parent_node
70
         * @param string      $attributes [optional]
71
         *
72
         * @return string
73
         */
74
        private static function buildXml($data, $parent_node, $attributes = null) {
75
            if (!is_array($data)) {
76
                return self::wrapNode($parent_node, $data, $attributes);
77
            }
78
79
            $result = '';
80
            $attributes = self::getAttributesString(array_get($data, '@attributes', []));
0 ignored issues
show
It seems like array_get($data, '@attributes', array()) can also be of type null; however, parameter $attributes of rAPId\Data\Serialization...::getAttributesString() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

80
            $attributes = self::getAttributesString(/** @scrutinizer ignore-type */ array_get($data, '@attributes', []));
Loading history...
81
            unset($data['@attributes']);
82
83
            foreach ($data as $key => $value) {
84
                if (is_numeric($key)) {
85
                    $result .= self::buildXml($value, null, $attributes);
86
                } else {
87
                    $result .= self::buildXml($value, $key);
88
                }
89
            }
90
91
            return self::wrapNode($parent_node, $result, $attributes);
92
        }
93
94
        /**
95
         * Wrap $content in an xml node
96
         * ie <node attrName="attrVal">content</node>
97
         *
98
         * @param string $name
99
         * @param string $content
100
         * @param string $attributes
101
         *
102
         * @return null|string
103
         */
104
        private static function wrapNode($name, $content = null, $attributes = '') {
105
            if (empty($name)) {
106
                return $content;
107
            }
108
109
            $node = "<$name";
110
            if ($attributes) {
111
                $node .= ' ' . trim($attributes);
112
            }
113
114
            if (empty($content)) {
115
                return $node . '/>';
116
            }
117
118
            return $node . '>' . $content . "</$name>";
119
        }
120
121
        /**
122
         * @param array $attributes
123
         *
124
         * @return null|string
125
         */
126
        private static function getAttributesString(array $attributes) {
127
            if (empty($attributes)) {
128
                return null;
129
            }
130
            $result = '';
131
            foreach ($attributes as $k => $v) {
132
                $result .= $k . '="' . $v . '" ';
133
            }
134
135
136
            return $result;
137
        }
138
139
        /**
140
         * @param SimpleXMLElement $xml
141
         *
142
         * @return array|mixed
143
         */
144
        private static function simpleXmlToArray(SimpleXMLElement $xml) {
145
146
            $array = json_decode(json_encode($xml), true);
147
148
            $array = self::ensureAttributes($array, $xml);
149
            $array = self::fixNullNodes($array);
150
151
            // In the case of <node>76</node>
152
            // just return 76
153
            if (count($array) == 1 && key($array) === 0) {
154
                return $array[0];
155
            }
156
157
            return $array;
158
        }
159
160
        /**
161
         * Calling json encode/decode on SimpleXml can result in missing attributes
162
         * This function recursively checks all simpleXml nodes for attributes and adds them
163
         * to the deserialized xml array
164
         *
165
         * @param array            $deserialized_xml
166
         * @param SimpleXMLElement $simple_xml
167
         *
168
         * @return array
169
         */
170
        private static function ensureAttributes(array $deserialized_xml, SimpleXMLElement $simple_xml) {
171
            foreach ($simple_xml as $node) {
172
                if (count($node->children()) == 0) {
173
                    $attributes = (array)$node->attributes();
174
                    $deserialized_xml = self::addAttributesToXmlArray($deserialized_xml, $node->getName(), $attributes);
175
                } else {
176
                    $deserialized_xml[ $node->getName() ] = self::ensureAttributes($deserialized_xml[ $node->getName() ], $node);
177
                }
178
            }
179
180
            return $deserialized_xml;
181
        }
182
183
        /**
184
         * @param array  $xml
185
         * @param string $name
186
         * @param array  $attributes
187
         *
188
         * @return array $xml
189
         */
190
        private static function addAttributesToXmlArray(array $xml, $name, array $attributes) {
191
            if (!empty($attributes) && !isset($xml[ $name ]['@attributes'])) {
192
                $xml[ $name ] = array_wrap($xml[ $name ]);
193
                $xml[ $name ]['@attributes'] = $attributes['@attributes'];
194
            }
195
196
            return $xml;
197
        }
198
199
        /**
200
         * json encode/decode results in <node/> being converted to [ 'node' => [] ]
201
         * when our expected behaviour is [ 'node' => null ]
202
         *
203
         * @param array $array
204
         *
205
         * @return array
206
         */
207
        private static function fixNullNodes(array $array) {
208
            foreach ($array as $k => $value) {
209
                if (empty($value)) {
210
                    $array[ $k ] = null;
211
                } else if (is_array($value)) {
212
                    $array[ $k ] = self::fixNullNodes($value);
213
                }
214
            }
215
216
            return $array;
217
        }
218
    }