Skip to content

Commit f81e204

Browse files
authored
Use XmlSerializer in XmlDataResponseFormatter
1 parent 5d4e040 commit f81e204

File tree

2 files changed

+192
-206
lines changed

2 files changed

+192
-206
lines changed

src/Formatter/XmlDataResponseFormatter.php

+46-124
Original file line numberDiff line numberDiff line change
@@ -4,68 +4,53 @@
44

55
namespace Yiisoft\DataResponse\Formatter;
66

7-
use DOMDocument;
8-
use DOMElement;
9-
use DOMException;
10-
use DOMText;
117
use Psr\Http\Message\ResponseInterface;
8+
use Traversable;
129
use Yiisoft\DataResponse\HasContentTypeTrait;
1310
use Yiisoft\Http\Header;
11+
use Yiisoft\Serializer\XmlSerializer;
1412
use Yiisoft\Strings\NumericHelper;
15-
use Yiisoft\Strings\StringHelper;
1613
use Yiisoft\DataResponse\DataResponse;
1714
use Yiisoft\DataResponse\DataResponseFormatterInterface;
1815

16+
use function is_array;
17+
use function is_float;
18+
use function is_scalar;
19+
1920
final class XmlDataResponseFormatter implements DataResponseFormatterInterface
2021
{
2122
use HasContentTypeTrait;
23+
2224
/**
23-
* @var string the Content-Type header for the response
25+
* @var string The Content-Type header for the response.
2426
*/
2527
private string $contentType = 'application/xml';
28+
2629
/**
27-
* @var string the XML version
30+
* @var string The XML version.
2831
*/
2932
private string $version = '1.0';
33+
3034
/**
31-
* @var string the XML encoding.
35+
* @var string The XML encoding.
3236
*/
3337
private string $encoding = 'UTF-8';
38+
3439
/**
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.
3641
*/
3742
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;
5143

5244
public function format(DataResponse $dataResponse): ResponseInterface
5345
{
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()));
6650
}
51+
6752
$response = $dataResponse->getResponse();
68-
$response->getBody()->write($content);
53+
$response->getBody()->write($content ?? '');
6954

7055
return $response->withHeader(Header::CONTENT_TYPE, $this->contentType . '; ' . $this->encoding);
7156
}
@@ -91,118 +76,55 @@ public function withRootTag(string $rootTag): self
9176
return $new;
9277
}
9378

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)
9586
{
96-
$new = clone $this;
97-
$new->itemTag = $itemTag;
98-
return $new;
99-
}
87+
if (is_scalar($data)) {
88+
return $this->formatScalarValue($data);
89+
}
10090

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+
}
10794

108-
public function withUseObjectTags(bool $useObjectTags): self
109-
{
110-
$new = clone $this;
111-
$new->useObjectTags = $useObjectTags;
112-
return $new;
113-
}
95+
$formattedData = [];
11496

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);
143100
}
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);
151103
}
104+
105+
return $formattedData;
152106
}
153107

154108
/**
155-
* Formats scalar value to use in XML text node.
109+
* Formats scalar value to use in XML node.
156110
*
157-
* @param int|string|bool|float $value a scalar value.
111+
* @param int|string|bool|float $value to format.
158112
* @return string string representation of the value.
159113
*/
160114
private function formatScalarValue($value): string
161115
{
162116
if ($value === true) {
163117
return 'true';
164118
}
119+
165120
if ($value === false) {
166121
return 'false';
167122
}
123+
168124
if (is_float($value)) {
169125
return NumericHelper::normalize($value);
170126
}
171-
return (string)$value;
172-
}
173127

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;
207129
}
208130
}

0 commit comments

Comments
 (0)