Skip to content

Commit 356e5dd

Browse files
authored
Merge pull request #33 from veewee/nillable-lists
Detect nillable lists
2 parents 0c16bc7 + 896cdba commit 356e5dd

File tree

7 files changed

+274
-5
lines changed

7 files changed

+274
-5
lines changed

src/Encoder/OptionalElementEncoder.php

+18-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
namespace Soap\Encoding\Encoder;
55

66
use Soap\Encoding\Xml\Node\Element;
7+
use Soap\Encoding\Xml\Node\ElementList;
78
use Soap\Encoding\Xml\Writer\NilAttributeBuilder;
89
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
910
use VeeWee\Reflecta\Iso\Iso;
1011
use VeeWee\Xml\Xmlns\Xmlns;
12+
use function count;
13+
use function is_string;
1114

1215
/**
1316
* @template T of mixed
@@ -31,6 +34,7 @@ public function iso(Context $context): Iso
3134
$type = $context->type;
3235
$meta = $type->getMeta();
3336
$elementIso = $this->elementEncoder->iso($context);
37+
$isList = $meta->isList()->unwrapOr(false);
3438

3539
if (!$meta->isNullable()->unwrapOr(false)) {
3640
return $elementIso;
@@ -48,17 +52,27 @@ public function iso(Context $context): Iso
4852
/**
4953
* @return T|null
5054
*/
51-
static function (Element|string $xml) use ($elementIso) : mixed {
55+
static function (ElementList|Element|string $xml) use ($elementIso, $isList) : mixed {
5256
if ($xml === '') {
5357
return null;
5458
}
5559

56-
$documentElement = ($xml instanceof Element ? $xml : Element::fromString($xml))->element();
57-
if ($documentElement->getAttributeNS(Xmlns::xsi()->value(), 'nil') === 'true') {
60+
$parsedXml = match(true) {
61+
$isList && is_string($xml) => ElementList::fromString('<list>'.$xml.'</list>'),
62+
is_string($xml) => Element::fromString($xml),
63+
default => $xml,
64+
};
65+
66+
$documentElement = match (true) {
67+
$parsedXml instanceof ElementList && count($parsedXml) === 1 => $parsedXml->elements()[0]->element(),
68+
$parsedXml instanceof Element => $parsedXml->element(),
69+
default => null
70+
};
71+
if ($documentElement && $documentElement->getAttributeNS(Xmlns::xsi()->value(), 'nil') === 'true') {
5872
return null;
5973
}
6074

61-
/** @var Iso<T|null, Element|non-empty-string> $elementIso */
75+
/** @var Iso<T|null, ElementList|Element|non-empty-string> $elementIso */
6276
return $elementIso->from($xml);
6377
}
6478
);

src/Xml/Node/ElementList.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
namespace Soap\Encoding\Xml\Node;
55

66
use Closure;
7+
use Countable;
78
use DOMElement;
89
use Soap\Encoding\Xml\Reader\DocumentToLookupArrayReader;
910
use Stringable;
1011
use VeeWee\Xml\Dom\Document;
12+
use function count;
1113
use function Psl\Iter\reduce;
1214
use function Psl\Vec\map;
1315
use function VeeWee\Xml\Dom\Locator\Element\children as readChildren;
1416

1517
/**
1618
* @psalm-import-type LookupArray from DocumentToLookupArrayReader
1719
*/
18-
final class ElementList implements Stringable
20+
final class ElementList implements Countable, Stringable
1921
{
2022
/** @var list<Element> */
2123
private array $elements;
@@ -111,4 +113,9 @@ public function __toString()
111113
{
112114
return $this->value();
113115
}
116+
117+
public function count(): int
118+
{
119+
return count($this->elements);
120+
}
114121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Test\PhpCompatibility\Implied;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use Soap\Encoding\Decoder;
8+
use Soap\Encoding\Driver;
9+
use Soap\Encoding\Encoder;
10+
use Soap\Encoding\Test\PhpCompatibility\AbstractCompatibilityTests;
11+
12+
#[CoversClass(Driver::class)]
13+
#[CoversClass(Encoder::class)]
14+
#[CoversClass(Decoder::class)]
15+
#[CoversClass(Encoder\AnyElementEncoder::class)]
16+
final class ImpliedSchema009Test extends AbstractCompatibilityTests
17+
{
18+
protected string $schema = <<<EOXML
19+
<element name="testType" minOccurs="0" maxOccurs="1" type="tns:ArrayOfCompany" />
20+
<complexType name="ArrayOfCompany">
21+
<sequence>
22+
<element minOccurs="0" maxOccurs="unbounded" name="Company" nillable="true" type="tns:Company" />
23+
</sequence>
24+
</complexType>
25+
<complexType name="Company">
26+
<sequence>
27+
<element minOccurs="1" maxOccurs="1" name="ID" type="xsd:int" />
28+
</sequence>
29+
</complexType>
30+
EOXML;
31+
protected string $type = 'type="tns:testType"';
32+
33+
protected function calculateParam(): mixed
34+
{
35+
return (object)[
36+
'Company' => [
37+
(object)['ID' => 0],
38+
(object)['ID' => 1],
39+
],
40+
];
41+
}
42+
43+
protected function expectXml(): string
44+
{
45+
return <<<XML
46+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
47+
xmlns:tns="http://test-uri/"
48+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
49+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
50+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
51+
<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
52+
<tns:test>
53+
<testParam xsi:type="tns:ArrayOfCompany">
54+
<Company xsi:type="tns:Company">
55+
<ID xsi:type="xsd:int">0</ID>
56+
</Company>
57+
<Company xsi:type="tns:Company">
58+
<ID xsi:type="xsd:int">1</ID>
59+
</Company>
60+
</testParam>
61+
</tns:test>
62+
</SOAP-ENV:Body>
63+
</SOAP-ENV:Envelope>
64+
XML;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Test\PhpCompatibility\Implied;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use Soap\Encoding\Decoder;
8+
use Soap\Encoding\Driver;
9+
use Soap\Encoding\Encoder;
10+
use Soap\Encoding\Test\PhpCompatibility\AbstractCompatibilityTests;
11+
12+
#[CoversClass(Driver::class)]
13+
#[CoversClass(Encoder::class)]
14+
#[CoversClass(Decoder::class)]
15+
#[CoversClass(Encoder\AnyElementEncoder::class)]
16+
final class ImpliedSchema010Test extends AbstractCompatibilityTests
17+
{
18+
protected string $schema = <<<EOXML
19+
<element name="testType" minOccurs="0" maxOccurs="1" type="tns:ArrayOfCompany" />
20+
<complexType name="ArrayOfCompany">
21+
<sequence>
22+
<element minOccurs="0" maxOccurs="unbounded" name="Company" nillable="true" type="tns:Company" />
23+
</sequence>
24+
</complexType>
25+
<complexType name="Company">
26+
<sequence>
27+
<element minOccurs="1" maxOccurs="1" name="ID" type="xsd:int" />
28+
</sequence>
29+
</complexType>
30+
EOXML;
31+
protected string $type = 'type="tns:testType"';
32+
33+
protected function calculateParam(): mixed
34+
{
35+
return (object) [
36+
'Company' => null,
37+
];
38+
}
39+
40+
protected function expectXml(): string
41+
{
42+
return <<<XML
43+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
44+
xmlns:tns="http://test-uri/"
45+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
46+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
47+
<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
48+
<tns:test>
49+
<testParam xsi:type="tns:ArrayOfCompany">
50+
<Company xsi:nil="true" />
51+
</testParam>
52+
</tns:test>
53+
</SOAP-ENV:Body>
54+
</SOAP-ENV:Envelope>
55+
XML;
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Test\PhpCompatibility\Implied;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use Soap\Encoding\Decoder;
8+
use Soap\Encoding\Driver;
9+
use Soap\Encoding\Encoder;
10+
use Soap\Encoding\Test\PhpCompatibility\AbstractCompatibilityTests;
11+
12+
#[CoversClass(Driver::class)]
13+
#[CoversClass(Encoder::class)]
14+
#[CoversClass(Decoder::class)]
15+
#[CoversClass(Encoder\AnyElementEncoder::class)]
16+
final class ImpliedSchema011Test extends AbstractCompatibilityTests
17+
{
18+
protected string $schema = <<<EOXML
19+
<element name="testType" minOccurs="0" maxOccurs="1" type="tns:ArrayOfCompany" />
20+
<complexType name="ArrayOfCompany">
21+
<sequence>
22+
<element minOccurs="0" maxOccurs="unbounded" name="Company" nillable="true" type="tns:Company" />
23+
</sequence>
24+
</complexType>
25+
<complexType name="Company">
26+
<sequence>
27+
<element minOccurs="1" maxOccurs="1" name="ID" type="xsd:int" />
28+
</sequence>
29+
</complexType>
30+
EOXML;
31+
protected string $type = 'type="tns:testType"';
32+
33+
protected function calculateParam(): mixed
34+
{
35+
return (object) [
36+
'Company' => [],
37+
];
38+
}
39+
40+
protected function expectXml(): string
41+
{
42+
return <<<XML
43+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
44+
xmlns:tns="http://test-uri/"
45+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
46+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
47+
<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
48+
<tns:test>
49+
<testParam xsi:type="tns:ArrayOfCompany" />
50+
</tns:test>
51+
</SOAP-ENV:Body>
52+
</SOAP-ENV:Envelope>
53+
XML;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Test\PhpCompatibility\Implied;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use Soap\Encoding\Decoder;
8+
use Soap\Encoding\Driver;
9+
use Soap\Encoding\Encoder;
10+
use Soap\Encoding\Test\PhpCompatibility\AbstractCompatibilityTests;
11+
12+
#[CoversClass(Driver::class)]
13+
#[CoversClass(Encoder::class)]
14+
#[CoversClass(Decoder::class)]
15+
#[CoversClass(Encoder\AnyElementEncoder::class)]
16+
final class ImpliedSchema012Test extends AbstractCompatibilityTests
17+
{
18+
protected string $schema = <<<EOXML
19+
<element name="testType" minOccurs="0" maxOccurs="1" type="tns:ArrayOfCompany" />
20+
<complexType name="ArrayOfCompany">
21+
<sequence>
22+
<element minOccurs="0" maxOccurs="unbounded" name="Company" nillable="true" type="tns:Company" />
23+
</sequence>
24+
</complexType>
25+
<complexType name="Company">
26+
<sequence>
27+
<element minOccurs="1" maxOccurs="1" name="ID" type="xsd:int" />
28+
</sequence>
29+
</complexType>
30+
EOXML;
31+
protected string $type = 'type="tns:testType"';
32+
33+
protected function calculateParam(): mixed
34+
{
35+
return (object) [
36+
'Company' => [
37+
(object)['ID' => 0],
38+
],
39+
];
40+
}
41+
42+
protected function expectXml(): string
43+
{
44+
return <<<XML
45+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
46+
xmlns:tns="http://test-uri/"
47+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
48+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
49+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
50+
<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
51+
<tns:test>
52+
<testParam xsi:type="tns:ArrayOfCompany">
53+
<Company xsi:type="tns:Company">
54+
<ID xsi:type="xsd:int">0</ID>
55+
</Company>
56+
</testParam>
57+
</tns:test>
58+
</SOAP-ENV:Body>
59+
</SOAP-ENV:Envelope>
60+
XML;
61+
}
62+
}

tests/Unit/Xml/Node/ElementListTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,12 @@ public function test_it_can_load_nested_list(): void
5555

5656
static::assertSame([$hello, $list1, $list2], $list->elements());
5757
}
58+
59+
public function test_it_can_be_countded(): void
60+
{
61+
$list = new ElementList(Element::fromString('<hello>world</hello>'));
62+
63+
static::assertCount(1, $list->elements());
64+
static::assertCount(1, $list);
65+
}
5866
}

0 commit comments

Comments
 (0)