Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0855dcc

Browse files
authoredJun 11, 2021
Add support for objects to XML formatting (#52)
1 parent f1ded59 commit 0855dcc

File tree

2 files changed

+175
-27
lines changed

2 files changed

+175
-27
lines changed
 

‎src/Formatter/XmlDataResponseFormatter.php

+27-17
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@
99
use DOMException;
1010
use DOMText;
1111
use Psr\Http\Message\ResponseInterface;
12-
use RuntimeException;
12+
use Traversable;
1313
use Yiisoft\DataResponse\ResponseContentTrait;
1414
use Yiisoft\Strings\NumericHelper;
1515
use Yiisoft\DataResponse\DataResponse;
1616
use Yiisoft\DataResponse\DataResponseFormatterInterface;
1717

18-
use function get_class;
1918
use function is_array;
2019
use function is_float;
2120
use function is_int;
2221
use function is_object;
23-
use function sprintf;
2422

2523
final class XmlDataResponseFormatter implements DataResponseFormatterInterface
2624
{
@@ -110,10 +108,10 @@ private function buildXml(DOMDocument $dom, $element, $data): void
110108
return;
111109
}
112110

113-
if (is_array($data)) {
111+
if (is_array($data) || ($data instanceof Traversable && !($data instanceof XmlDataInterface))) {
114112
foreach ($data as $name => $value) {
115113
if (is_object($value)) {
116-
$this->buildObject($dom, $element, $value);
114+
$this->buildObject($dom, $element, $value, $name);
117115
continue;
118116
}
119117

@@ -145,25 +143,37 @@ private function buildXml(DOMDocument $dom, $element, $data): void
145143
* @param DOMDocument $dom The root DOM document.
146144
* @param DOMDocument|DOMElement $element The current DOM element being processed.
147145
* @param object $object To build.
146+
* @param int|string|null $tagName The tag name.
148147
*/
149-
private function buildObject(DOMDocument $dom, $element, object $object): void
148+
private function buildObject(DOMDocument $dom, $element, object $object, $tagName = null): void
150149
{
151-
if (!($object instanceof XmlDataInterface)) {
152-
throw new RuntimeException(sprintf(
153-
'The "%s" object must implement the "%s" interface.',
154-
get_class($object),
155-
XmlDataInterface::class,
156-
));
157-
}
150+
if ($object instanceof XmlDataInterface) {
151+
$child = $this->safeCreateDomElement($dom, $object->xmlTagName());
158152

159-
$child = $this->safeCreateDomElement($dom, $object->xmlTagName());
153+
foreach ($object->xmlTagAttributes() as $name => $value) {
154+
$child->setAttribute($name, $value);
155+
}
160156

161-
foreach ($object->xmlTagAttributes() as $name => $value) {
162-
$child->setAttribute($name, $value);
157+
$element->appendChild($child);
158+
$this->buildXml($dom, $child, $object->xmlData());
159+
return;
163160
}
164161

162+
$child = $this->safeCreateDomElement($dom, $tagName);
165163
$element->appendChild($child);
166-
$this->buildXml($dom, $child, $object->xmlData());
164+
165+
if ($object instanceof Traversable) {
166+
$this->buildXml($dom, $child, $object);
167+
return;
168+
}
169+
170+
$data = [];
171+
172+
foreach ($object as $property => $value) {
173+
$data[$property] = $value;
174+
}
175+
176+
$this->buildXml($dom, $child, $data);
167177
}
168178

169179
/**

‎tests/Formatter/XmlDataResponseFormatterTest.php

+148-10
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
namespace Yiisoft\DataResponse\Tests\Formatter;
66

7+
use ArrayObject;
78
use Nyholm\Psr7\Factory\Psr17Factory;
89
use PHPUnit\Framework\TestCase;
9-
use RuntimeException;
1010
use stdClass;
1111
use Yiisoft\DataResponse\DataResponse;
1212
use Yiisoft\DataResponse\DataResponseFactory;
@@ -163,7 +163,154 @@ public function testArrayValues(): void
163163
);
164164
}
165165

166+
public function testTraversableValue(): void
167+
{
168+
$dataResponse = $this->createResponse(['array-value' => new ArrayObject(['test', null])]);
169+
$result = (new XmlDataResponseFormatter())->format($dataResponse);
170+
$result->getBody()->rewind();
171+
172+
$this->assertSame(
173+
$this->xml(
174+
<<<EOF
175+
<response>
176+
<array-value>
177+
<item>test</item>
178+
<item/>
179+
</array-value>
180+
</response>
181+
EOF
182+
),
183+
$result->getBody()->getContents()
184+
);
185+
}
186+
187+
public function testTraversableValues(): void
188+
{
189+
$dataResponse = $this->createResponse([
190+
new ArrayObject(['foo' => 'bar', null]),
191+
new ArrayObject([true, false]),
192+
new ArrayObject(['array' => [
193+
new ArrayObject(['test', null]),
194+
new ArrayObject([true, false]),
195+
]]),
196+
]);
197+
$result = (new XmlDataResponseFormatter())->format($dataResponse);
198+
$result->getBody()->rewind();
199+
200+
$this->assertSame(
201+
$this->xml(
202+
<<<EOF
203+
<response>
204+
<item>
205+
<foo>bar</foo>
206+
<item/>
207+
</item>
208+
<item>
209+
<item>true</item>
210+
<item>false</item>
211+
</item>
212+
<item>
213+
<array>
214+
<item>
215+
<item>test</item>
216+
<item/>
217+
</item>
218+
<item>
219+
<item>true</item>
220+
<item>false</item>
221+
</item>
222+
</array>
223+
</item>
224+
</response>
225+
EOF
226+
),
227+
$result->getBody()->getContents()
228+
);
229+
}
230+
231+
public function testPriorityXmlDataInterfaceOverTraversable(): void
232+
{
233+
$dataResponse = $this->createResponse(new class(['foo']) extends ArrayObject implements XmlDataInterface {
234+
public function xmlTagName(): string
235+
{
236+
return 'xml-data';
237+
}
238+
239+
public function xmlTagAttributes(): array
240+
{
241+
return ['attribute' => 'test'];
242+
}
243+
244+
public function xmlData(): array
245+
{
246+
return ['foo' => 'bar'];
247+
}
248+
});
249+
$result = (new XmlDataResponseFormatter())->format($dataResponse);
250+
$result->getBody()->rewind();
251+
252+
$this->assertSame(
253+
$this->xml(
254+
<<<EOF
255+
<response>
256+
<xml-data attribute="test">
257+
<foo>bar</foo>
258+
</xml-data>
259+
</response>
260+
EOF
261+
),
262+
$result->getBody()->getContents()
263+
);
264+
}
265+
266+
public function testPriorityXmlDataInterfaceOverTraversableInArray(): void
267+
{
268+
$dataResponse = $this->createResponse([new class(['foo']) extends ArrayObject implements XmlDataInterface {
269+
public function xmlTagName(): string
270+
{
271+
return 'xml-data';
272+
}
273+
274+
public function xmlTagAttributes(): array
275+
{
276+
return ['attribute' => 'test'];
277+
}
278+
279+
public function xmlData(): array
280+
{
281+
return ['foo' => 'bar'];
282+
}
283+
}]);
284+
$result = (new XmlDataResponseFormatter())->format($dataResponse);
285+
$result->getBody()->rewind();
286+
287+
$this->assertSame(
288+
$this->xml(
289+
<<<EOF
290+
<response>
291+
<xml-data attribute="test">
292+
<foo>bar</foo>
293+
</xml-data>
294+
</response>
295+
EOF
296+
),
297+
$result->getBody()->getContents()
298+
);
299+
}
300+
166301
public function testEmptyObjectValues(): void
302+
{
303+
$dataResponse = $this->createResponse(['object' => new stdClass()]);
304+
$result = (new XmlDataResponseFormatter())->format($dataResponse);
305+
$result->getBody()->rewind();
306+
307+
$this->assertSame(
308+
$this->xml('<response><object/></response>'),
309+
$result->getBody()->getContents()
310+
);
311+
}
312+
313+
public function testEmptyObjectValuesImplementXmlDataInterface(): void
167314
{
168315
$dataResponse = $this->createResponse(['object' => new class() implements XmlDataInterface {
169316
public function xmlTagName(): string
@@ -217,15 +364,6 @@ public function testObjectValues(): void
217364
);
218365
}
219366

220-
public function testObjectNotImplementXmlFormatDataInterface(): void
221-
{
222-
$dataResponse = $this->createResponse(new stdClass());
223-
224-
$this->expectException(RuntimeException::class);
225-
226-
(new XmlDataResponseFormatter())->format($dataResponse);
227-
}
228-
229367
public function testArrayObjectValues(): void
230368
{
231369
$objects = [

0 commit comments

Comments
 (0)
Failed to load comments.