4
4
5
5
namespace Yiisoft \DataResponse \Formatter ;
6
6
7
- use DOMDocument ;
8
- use DOMElement ;
9
- use DOMException ;
10
- use DOMText ;
11
7
use Psr \Http \Message \ResponseInterface ;
8
+ use Traversable ;
12
9
use Yiisoft \DataResponse \HasContentTypeTrait ;
13
10
use Yiisoft \Http \Header ;
11
+ use Yiisoft \Serializer \XmlSerializer ;
14
12
use Yiisoft \Strings \NumericHelper ;
15
- use Yiisoft \Strings \StringHelper ;
16
13
use Yiisoft \DataResponse \DataResponse ;
17
14
use Yiisoft \DataResponse \DataResponseFormatterInterface ;
18
15
16
+ use function is_array ;
17
+ use function is_float ;
18
+ use function is_scalar ;
19
+
19
20
final class XmlDataResponseFormatter implements DataResponseFormatterInterface
20
21
{
21
22
use HasContentTypeTrait;
23
+
22
24
/**
23
- * @var string the Content-Type header for the response
25
+ * @var string The Content-Type header for the response.
24
26
*/
25
27
private string $ contentType = 'application/xml ' ;
28
+
26
29
/**
27
- * @var string the XML version
30
+ * @var string The XML version.
28
31
*/
29
32
private string $ version = '1.0 ' ;
33
+
30
34
/**
31
- * @var string the XML encoding.
35
+ * @var string The XML encoding.
32
36
*/
33
37
private string $ encoding = 'UTF-8 ' ;
38
+
34
39
/**
35
- * @var string the name of the root element. If set to false, null or is empty then no root tag should be added.
40
+ * @var string The name of the root element. If set to false, null or is empty then no root tag should be added.
36
41
*/
37
42
private string $ rootTag = 'response ' ;
38
- /**
39
- * @var string the name of the elements that represent the array elements with numeric keys.
40
- */
41
- private string $ itemTag = 'item ' ;
42
- /**
43
- * @var bool whether to interpret objects implementing the [[\Traversable]] interface as arrays.
44
- * Defaults to `true`.
45
- */
46
- private bool $ useTraversableAsArray = true ;
47
- /**
48
- * @var bool if object tags should be added
49
- */
50
- private bool $ useObjectTags = true ;
51
43
52
44
public function format (DataResponse $ dataResponse ): ResponseInterface
53
45
{
54
- $ content = '' ;
55
- $ data = $ dataResponse ->getData ();
56
- if ($ data !== null ) {
57
- $ dom = new DOMDocument ($ this ->version , $ this ->encoding );
58
- if (!empty ($ this ->rootTag )) {
59
- $ root = new DOMElement ($ this ->rootTag );
60
- $ dom ->appendChild ($ root );
61
- $ this ->buildXml ($ root , $ data );
62
- } else {
63
- $ this ->buildXml ($ dom , $ data );
64
- }
65
- $ content = $ dom ->saveXML ();
46
+ $ serializer = new XmlSerializer ($ this ->rootTag , $ this ->version , $ this ->encoding );
47
+
48
+ if ($ dataResponse ->hasData ()) {
49
+ $ content = $ serializer ->serialize ($ this ->formatData ($ dataResponse ->getData ()));
66
50
}
51
+
67
52
$ response = $ dataResponse ->getResponse ();
68
- $ response ->getBody ()->write ($ content );
53
+ $ response ->getBody ()->write ($ content ?? '' );
69
54
70
55
return $ response ->withHeader (Header::CONTENT_TYPE , $ this ->contentType . '; ' . $ this ->encoding );
71
56
}
@@ -91,118 +76,55 @@ public function withRootTag(string $rootTag): self
91
76
return $ new ;
92
77
}
93
78
94
- public function withItemTag (string $ itemTag ): self
79
+ /**
80
+ * Pre-formats the data before serialization.
81
+ *
82
+ * @param mixed $data to format.
83
+ * @return mixed formatted data.
84
+ */
85
+ private function formatData ($ data )
95
86
{
96
- $ new = clone $ this ;
97
- $ new ->itemTag = $ itemTag ;
98
- return $ new ;
99
- }
87
+ if (is_scalar ($ data )) {
88
+ return $ this ->formatScalarValue ($ data );
89
+ }
100
90
101
- public function withUseTraversableAsArray (bool $ useTraversableAsArray ): self
102
- {
103
- $ new = clone $ this ;
104
- $ new ->useTraversableAsArray = $ useTraversableAsArray ;
105
- return $ new ;
106
- }
91
+ if (!is_array ($ data ) && !($ data instanceof Traversable)) {
92
+ return $ data ;
93
+ }
107
94
108
- public function withUseObjectTags (bool $ useObjectTags ): self
109
- {
110
- $ new = clone $ this ;
111
- $ new ->useObjectTags = $ useObjectTags ;
112
- return $ new ;
113
- }
95
+ $ formattedData = [];
114
96
115
- /**
116
- * @param DOMElement|DOMDocument $element
117
- * @param mixed $data
118
- */
119
- private function buildXml ($ element , $ data ): void
120
- {
121
- if (is_array ($ data ) ||
122
- ($ this ->useTraversableAsArray && $ data instanceof \Traversable)
123
- ) {
124
- foreach ($ data as $ name => $ value ) {
125
- if (is_int ($ name ) && is_object ($ value )) {
126
- $ this ->buildXml ($ element , $ value );
127
- } elseif (is_array ($ value ) || is_object ($ value )) {
128
- $ child = new DOMElement ($ this ->getValidXmlElementName ($ name ));
129
- $ element ->appendChild ($ child );
130
- $ this ->buildXml ($ child , $ value );
131
- } else {
132
- $ child = new DOMElement ($ this ->getValidXmlElementName ($ name ));
133
- $ element ->appendChild ($ child );
134
- $ child ->appendChild (new DOMText ($ this ->formatScalarValue ($ value )));
135
- }
136
- }
137
- } elseif (is_object ($ data )) {
138
- if ($ this ->useObjectTags ) {
139
- $ child = new DOMElement (StringHelper::baseName (get_class ($ data )));
140
- $ element ->appendChild ($ child );
141
- } else {
142
- $ child = $ element ;
97
+ foreach ($ data as $ key => $ value ) {
98
+ if (is_scalar ($ value )) {
99
+ $ formattedData [$ key ] = $ this ->formatScalarValue ($ value );
143
100
}
144
- $ array = [];
145
- foreach ($ data as $ name => $ value ) {
146
- $ array [$ name ] = $ value ;
147
- }
148
- $ this ->buildXml ($ child , $ array );
149
- } else {
150
- $ element ->appendChild (new DOMText ($ this ->formatScalarValue ($ data )));
101
+
102
+ $ formattedData [$ key ] = $ this ->formatData ($ value );
151
103
}
104
+
105
+ return $ formattedData ;
152
106
}
153
107
154
108
/**
155
- * Formats scalar value to use in XML text node.
109
+ * Formats scalar value to use in XML node.
156
110
*
157
- * @param int|string|bool|float $value a scalar value .
111
+ * @param int|string|bool|float $value to format .
158
112
* @return string string representation of the value.
159
113
*/
160
114
private function formatScalarValue ($ value ): string
161
115
{
162
116
if ($ value === true ) {
163
117
return 'true ' ;
164
118
}
119
+
165
120
if ($ value === false ) {
166
121
return 'false ' ;
167
122
}
123
+
168
124
if (is_float ($ value )) {
169
125
return NumericHelper::normalize ($ value );
170
126
}
171
- return (string )$ value ;
172
- }
173
127
174
- /**
175
- * Returns element name ready to be used in DOMElement if
176
- * name is not empty, is not int and is valid.
177
- *
178
- * Falls back to [[itemTag]] otherwise.
179
- *
180
- * @param mixed $name
181
- * @return string
182
- */
183
- private function getValidXmlElementName ($ name ): string
184
- {
185
- if (empty ($ name ) || is_int ($ name ) || !$ this ->isValidXmlName ($ name )) {
186
- return $ this ->itemTag ;
187
- }
188
-
189
- return $ name ;
190
- }
191
-
192
- /**
193
- * Checks if name is valid to be used in XML.
194
- *
195
- * @param mixed $name
196
- * @return bool
197
- * @see http://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943
198
- */
199
- private function isValidXmlName ($ name ): bool
200
- {
201
- try {
202
- new DOMElement ($ name );
203
- } catch (DOMException $ e ) {
204
- return false ;
205
- }
206
- return true ;
128
+ return (string ) $ value ;
207
129
}
208
130
}
0 commit comments