1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Yiisoft\Yii\Web\Data\Formatter; |
||||||
6 | |||||||
7 | use DOMDocument; |
||||||
8 | use DOMElement; |
||||||
9 | use DOMException; |
||||||
10 | use DOMText; |
||||||
11 | use Psr\Http\Message\ResponseInterface; |
||||||
12 | use Yiisoft\Http\Header; |
||||||
13 | use Yiisoft\Strings\StringHelper; |
||||||
14 | use Yiisoft\Yii\Web\Data\DataResponse; |
||||||
15 | use Yiisoft\Yii\Web\Data\DataResponseFormatterInterface; |
||||||
16 | |||||||
17 | final class XmlDataResponseFormatter implements DataResponseFormatterInterface |
||||||
18 | { |
||||||
19 | /** |
||||||
20 | * @var string the Content-Type header for the response |
||||||
21 | */ |
||||||
22 | private string $contentType = 'application/xml'; |
||||||
23 | /** |
||||||
24 | * @var string the XML version |
||||||
25 | */ |
||||||
26 | private string $version = '1.0'; |
||||||
27 | /** |
||||||
28 | * @var string the XML encoding. |
||||||
29 | */ |
||||||
30 | private string $encoding = 'UTF-8'; |
||||||
31 | /** |
||||||
32 | * @var string the name of the root element. If set to false, null or is empty then no root tag should be added. |
||||||
33 | */ |
||||||
34 | private string $rootTag = 'response'; |
||||||
35 | /** |
||||||
36 | * @var string the name of the elements that represent the array elements with numeric keys. |
||||||
37 | */ |
||||||
38 | private string $itemTag = 'item'; |
||||||
39 | /** |
||||||
40 | * @var bool whether to interpret objects implementing the [[\Traversable]] interface as arrays. |
||||||
41 | * Defaults to `true`. |
||||||
42 | */ |
||||||
43 | private bool $useTraversableAsArray = true; |
||||||
44 | /** |
||||||
45 | * @var bool if object tags should be added |
||||||
46 | */ |
||||||
47 | private bool $useObjectTags = true; |
||||||
48 | |||||||
49 | 8 | public function format(DataResponse $dataResponse): ResponseInterface |
|||||
50 | { |
||||||
51 | 8 | $content = ''; |
|||||
52 | 8 | $data = $dataResponse->getData(); |
|||||
53 | 8 | if ($data !== null) { |
|||||
54 | 8 | $dom = new DOMDocument($this->version, $this->encoding); |
|||||
55 | 8 | if (!empty($this->rootTag)) { |
|||||
56 | 8 | $root = new DOMElement($this->rootTag); |
|||||
0 ignored issues
–
show
|
|||||||
57 | 8 | $dom->appendChild($root); |
|||||
58 | 8 | $this->buildXml($root, $data); |
|||||
59 | } else { |
||||||
60 | $this->buildXml($dom, $data); |
||||||
0 ignored issues
–
show
$dom of type DOMDocument is incompatible with the type DOMElement expected by parameter $element of Yiisoft\Yii\Web\Data\For...seFormatter::buildXml() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
61 | } |
||||||
62 | 8 | $content = $dom->saveXML(); |
|||||
63 | } |
||||||
64 | 8 | $response = $dataResponse->getResponse(); |
|||||
65 | 8 | $response->getBody()->write($content); |
|||||
66 | |||||||
67 | 8 | return $response->withHeader(Header::CONTENT_TYPE, $this->contentType . '; ' . $this->encoding); |
|||||
68 | } |
||||||
69 | |||||||
70 | 1 | public function withVersion(string $version): self |
|||||
71 | { |
||||||
72 | 1 | $formatter = clone $this; |
|||||
73 | 1 | $formatter->version = $version; |
|||||
74 | 1 | return $formatter; |
|||||
75 | } |
||||||
76 | |||||||
77 | 1 | public function withEncoding(string $encoding): self |
|||||
78 | { |
||||||
79 | 1 | $formatter = clone $this; |
|||||
80 | 1 | $formatter->encoding = $encoding; |
|||||
81 | 1 | return $formatter; |
|||||
82 | } |
||||||
83 | |||||||
84 | 1 | public function withRootTag(string $rootTag): self |
|||||
85 | { |
||||||
86 | 1 | $formatter = clone $this; |
|||||
87 | 1 | $formatter->rootTag = $rootTag; |
|||||
88 | 1 | return $formatter; |
|||||
89 | } |
||||||
90 | |||||||
91 | 1 | public function withItemTag(string $itemTag): self |
|||||
92 | { |
||||||
93 | 1 | $formatter = clone $this; |
|||||
94 | 1 | $formatter->itemTag = $itemTag; |
|||||
95 | 1 | return $formatter; |
|||||
96 | } |
||||||
97 | |||||||
98 | public function withUseTraversableAsArray(bool $useTraversableAsArray): self |
||||||
99 | { |
||||||
100 | $formatter = clone $this; |
||||||
101 | $formatter->useTraversableAsArray = $useTraversableAsArray; |
||||||
102 | return $formatter; |
||||||
103 | } |
||||||
104 | |||||||
105 | 2 | public function withUseObjectTags(bool $useObjectTags): self |
|||||
106 | { |
||||||
107 | 2 | $formatter = clone $this; |
|||||
108 | 2 | $formatter->useObjectTags = $useObjectTags; |
|||||
109 | 2 | return $formatter; |
|||||
110 | } |
||||||
111 | |||||||
112 | 1 | public function withContentType(string $contentType): self |
|||||
113 | { |
||||||
114 | 1 | $formatter = clone $this; |
|||||
115 | 1 | $formatter->contentType = $contentType; |
|||||
116 | 1 | return $formatter; |
|||||
117 | } |
||||||
118 | |||||||
119 | /** |
||||||
120 | * @param DOMElement $element |
||||||
121 | * @param mixed $data |
||||||
122 | */ |
||||||
123 | 8 | private function buildXml($element, $data): void |
|||||
124 | { |
||||||
125 | 8 | if (is_array($data) || |
|||||
126 | 8 | ($this->useTraversableAsArray && $data instanceof \Traversable) |
|||||
127 | ) { |
||||||
128 | 3 | foreach ($data as $name => $value) { |
|||||
129 | 3 | if (is_int($name) && is_object($value)) { |
|||||
130 | $this->buildXml($element, $value); |
||||||
131 | 3 | } elseif (is_array($value) || is_object($value)) { |
|||||
132 | $child = new DOMElement($this->getValidXmlElementName($name)); |
||||||
0 ignored issues
–
show
The call to
DOMElement::__construct() has too few arguments starting with value .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.
Loading history...
|
|||||||
133 | $element->appendChild($child); |
||||||
134 | $this->buildXml($child, $value); |
||||||
135 | } else { |
||||||
136 | 3 | $child = new DOMElement($this->getValidXmlElementName($name)); |
|||||
137 | 3 | $element->appendChild($child); |
|||||
138 | 3 | $child->appendChild(new DOMText($this->formatScalarValue($value))); |
|||||
139 | } |
||||||
140 | } |
||||||
141 | 7 | } elseif (is_object($data)) { |
|||||
142 | 2 | if ($this->useObjectTags) { |
|||||
143 | 1 | $child = new DOMElement(StringHelper::basename(get_class($data))); |
|||||
144 | 1 | $element->appendChild($child); |
|||||
145 | } else { |
||||||
146 | 1 | $child = $element; |
|||||
147 | } |
||||||
148 | 2 | $array = []; |
|||||
149 | 2 | foreach ($data as $name => $value) { |
|||||
150 | 2 | $array[$name] = $value; |
|||||
151 | } |
||||||
152 | 2 | $this->buildXml($child, $array); |
|||||
153 | } else { |
||||||
154 | 5 | $element->appendChild(new DOMText($this->formatScalarValue($data))); |
|||||
155 | } |
||||||
156 | 8 | } |
|||||
157 | |||||||
158 | /** |
||||||
159 | * Formats scalar value to use in XML text node. |
||||||
160 | * |
||||||
161 | * @param int|string|bool|float $value a scalar value. |
||||||
162 | * @return string string representation of the value. |
||||||
163 | */ |
||||||
164 | 8 | private function formatScalarValue($value): string |
|||||
165 | { |
||||||
166 | 8 | if ($value === true) { |
|||||
167 | return 'true'; |
||||||
168 | } |
||||||
169 | 8 | if ($value === false) { |
|||||
170 | return 'false'; |
||||||
171 | } |
||||||
172 | 8 | if (is_float($value)) { |
|||||
173 | return StringHelper::floatToString($value); |
||||||
174 | } |
||||||
175 | 8 | return (string)$value; |
|||||
176 | } |
||||||
177 | |||||||
178 | /** |
||||||
179 | * Returns element name ready to be used in DOMElement if |
||||||
180 | * name is not empty, is not int and is valid. |
||||||
181 | * |
||||||
182 | * Falls back to [[itemTag]] otherwise. |
||||||
183 | * |
||||||
184 | * @param mixed $name |
||||||
185 | * @return string |
||||||
186 | */ |
||||||
187 | 3 | private function getValidXmlElementName($name): string |
|||||
188 | { |
||||||
189 | 3 | if (empty($name) || is_int($name) || !$this->isValidXmlName($name)) { |
|||||
190 | 1 | return $this->itemTag; |
|||||
191 | } |
||||||
192 | |||||||
193 | 3 | return $name; |
|||||
194 | } |
||||||
195 | |||||||
196 | /** |
||||||
197 | * Checks if name is valid to be used in XML. |
||||||
198 | * |
||||||
199 | * @param mixed $name |
||||||
200 | * @return bool |
||||||
201 | * @see http://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943 |
||||||
202 | */ |
||||||
203 | 3 | private function isValidXmlName($name): bool |
|||||
204 | { |
||||||
205 | try { |
||||||
206 | 3 | new DOMElement($name); |
|||||
0 ignored issues
–
show
The call to
DOMElement::__construct() has too few arguments starting with value .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.
Loading history...
|
|||||||
207 | 1 | } catch (DOMException $e) { |
|||||
208 | 1 | return false; |
|||||
209 | } |
||||||
210 | 3 | return true; |
|||||
211 | } |
||||||
212 | } |
||||||
213 |
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.