Skip to content

Commit 20806cc

Browse files
committed
- bugfix: invalid key values are transformed to <item key="">...</item> nodes
1 parent 44dd08c commit 20806cc

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

Serializer/XmlSerializer.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818
use InvalidArgumentException;
1919
use Koded\Stdlib\Serializer;
2020
use Throwable;
21-
use function array_keys;
21+
use function array_is_list;
2222
use function count;
23-
use function ctype_digit;
2423
use function current;
2524
use function error_log;
2625
use function filter_var;
@@ -31,10 +30,10 @@
3130
use function is_iterable;
3231
use function is_numeric;
3332
use function is_object;
34-
use function join;
3533
use function Koded\Stdlib\{json_serialize, json_unserialize};
3634
use function key;
3735
use function preg_match;
36+
use function str_contains;
3837
use function str_starts_with;
3938
use function substr;
4039
use function trim;
@@ -130,14 +129,26 @@ private function buildXml(
130129
// node value
131130
$parent->nodeValue = $data;
132131
} elseif (false === $isKeyNumeric && is_array($data)) {
133-
if (ctype_digit(join('', array_keys($data)))) {
132+
/*
133+
* If the data is an associative array (with numeric keys)
134+
* the structure is transformed to "item" nodes:
135+
* <item key="0">$key0</item>
136+
* <item key="1">$key1</item>
137+
* by appending it to the parent node (if any)
138+
*/
139+
if (array_is_list($data)) {
134140
foreach ($data as $d) {
135141
$this->appendNode($document, $parent, $d, $key);
136142
}
137143
} else {
138144
$this->appendNode($document, $parent, $data, $key);
139145
}
140-
} elseif ($isKeyNumeric) {
146+
} elseif ($isKeyNumeric || false === $this->hasValidName($key)) {
147+
/* If the key is not a valid XML tag name,
148+
* transform the key to "item" node:
149+
* <item key="$key">$value</item>
150+
* by appending it to the parent node (if any)
151+
*/
141152
$this->appendNode($document, $parent, $data, 'item', $key);
142153
} else {
143154
$this->appendNode($document, $parent, $data, $key);
@@ -307,4 +318,11 @@ private function getValueByType(mixed $value): mixed
307318
}
308319
return $value[$this->val];
309320
}
321+
322+
private function hasValidName(int|string $key): bool
323+
{
324+
return $key &&
325+
!str_contains($key, ' ') &&
326+
preg_match('~^[\pL_][\pL0-9._:-]*$~ui', $key);
327+
}
310328
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Tests\Koded\Stdlib\Serializer;
4+
5+
use Koded\Stdlib\Serializer\XmlSerializer;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class XmlSerializationWithInvalidKeyNamesTest extends TestCase
9+
{
10+
private XmlSerializer $serializer;
11+
12+
private string $xml = <<<XML
13+
<?xml version="1.0"?>
14+
<response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
15+
<explain>
16+
<item key="/geo/ip\\.(json|xml)">Returns the client IP geo information</item>
17+
<item key="/geo/(IP_ADDRESS).(json|xml)">Returns the IP address geo information</item>
18+
<item key="/geo/">The &lt;index&gt; route</item>
19+
</explain>
20+
</response>
21+
XML;
22+
23+
private array $data = [
24+
'explain' => [
25+
'/geo/ip\.(json|xml)' => 'Returns the client IP geo information',
26+
'/geo/(IP_ADDRESS).(json|xml)' => 'Returns the IP address geo information',
27+
'/geo/' => 'The <index> route',
28+
]
29+
];
30+
31+
public function test_bug_for_serialization()
32+
{
33+
$this->assertXmlStringEqualsXmlString(
34+
$this->xml,
35+
$this->serializer->serialize($this->data)
36+
);
37+
}
38+
39+
public function test_bug_for_deserialization()
40+
{
41+
$this->assertTrue(
42+
$this->data === $this->serializer->unserialize($this->xml)
43+
);
44+
}
45+
46+
protected function setUp(): void
47+
{
48+
$this->serializer = new XmlSerializer('response');
49+
}
50+
}

0 commit comments

Comments
 (0)