diff --git a/src/Encoder/EncoderDetector.php b/src/Encoder/EncoderDetector.php index 63f9e11..2bab86c 100644 --- a/src/Encoder/EncoderDetector.php +++ b/src/Encoder/EncoderDetector.php @@ -51,6 +51,10 @@ public function __invoke(Context $context): XmlEncoder $encoder = new RepeatingElementEncoder($encoder); } + if (!$encoder instanceof Feature\OptionalAware && $meta->isNullable()->unwrapOr(false)) { + $encoder = new OptionalElementEncoder($encoder); + } + $encoder = new ErrorHandlingEncoder($encoder); return $this->cache[$type] = $encoder; diff --git a/src/Encoder/Feature/ListAware.php b/src/Encoder/Feature/ListAware.php index 81c7643..dd298be 100644 --- a/src/Encoder/Feature/ListAware.php +++ b/src/Encoder/Feature/ListAware.php @@ -3,6 +3,9 @@ namespace Soap\Encoding\Encoder\Feature; +/** + * Tells the encoder knows how to teal with list inputs. + */ interface ListAware extends DisregardXsiInformation { } diff --git a/src/Encoder/Feature/OptionalAware.php b/src/Encoder/Feature/OptionalAware.php new file mode 100644 index 0000000..f5d3874 --- /dev/null +++ b/src/Encoder/Feature/OptionalAware.php @@ -0,0 +1,11 @@ + */ -final class OptionalElementEncoder implements XmlEncoder +final class OptionalElementEncoder implements Feature\OptionalAware, XmlEncoder { /** * @param XmlEncoder $elementEncoder diff --git a/src/Encoder/RepeatingElementEncoder.php b/src/Encoder/RepeatingElementEncoder.php index f32069f..445fc82 100644 --- a/src/Encoder/RepeatingElementEncoder.php +++ b/src/Encoder/RepeatingElementEncoder.php @@ -12,7 +12,7 @@ /** * @template T - * @implements XmlEncoder, string> + * @implements XmlEncoder|null, string> */ final class RepeatingElementEncoder implements Feature\ListAware, XmlEncoder { @@ -25,7 +25,7 @@ public function __construct( } /** - * @return Iso, string> + * @return Iso|null, string> */ public function iso(Context $context): Iso { @@ -39,12 +39,12 @@ public function iso(Context $context): Iso return new Iso( /** - * @param iterable $raw + * @param iterable|null $raw */ - static function (iterable $raw) use ($innerIso): string { + static function (iterable|null $raw) use ($innerIso): string { return join( map( - $raw, + $raw ?? [], /** * @param T $item */ diff --git a/src/Encoder/SimpleType/EncoderDetector.php b/src/Encoder/SimpleType/EncoderDetector.php index 79baa61..c7a7193 100644 --- a/src/Encoder/SimpleType/EncoderDetector.php +++ b/src/Encoder/SimpleType/EncoderDetector.php @@ -39,9 +39,11 @@ public function __invoke(Context $context): XmlEncoder } if ($meta->isElement()->unwrapOr(false)) { - $encoder = new OptionalElementEncoder( - new ElementEncoder($encoder) - ); + $encoder = new ElementEncoder($encoder); + + if ($meta->isNullable()->unwrapOr(false)) { + $encoder = new OptionalElementEncoder($encoder); + } } return $encoder; diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema004Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema004Test.php new file mode 100644 index 0000000..a0292cc --- /dev/null +++ b/tests/PhpCompatibility/Implied/ImpliedSchema004Test.php @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + EOXML; + protected string $type = 'type="tns:testType"'; + + protected function calculateParam(): mixed + { + return (object)[]; + } + + protected function expectDecoded(): mixed + { + return (object)[ + 'OptionalList' => [], + 'OptionalSimpleElement' => null, + 'OptionalObject' => null, + ]; + } + + protected function expectXml(): string + { + return << + + + + + + + XML; + } +} diff --git a/tests/Unit/Encoder/RepeatingElementEncoderTest.php b/tests/Unit/Encoder/RepeatingElementEncoderTest.php index 9b082d9..1230414 100644 --- a/tests/Unit/Encoder/RepeatingElementEncoderTest.php +++ b/tests/Unit/Encoder/RepeatingElementEncoderTest.php @@ -82,4 +82,19 @@ public function test_it_can_decode_from_xml_item_list(): void static::assertEquals(['world'], $actual); } + + public function test_it_can_encode_from_null(): void + { + $encoder = new RepeatingElementEncoder(new ElementEncoder(new StringTypeEncoder())); + $context = self::createContext( + XsdType::guess('string') + ->withXmlTargetNodeName('hello') + ->withMeta(static fn (TypeMeta $meta): TypeMeta => $meta->withIsQualified(true)) + ); + + $iso = $encoder->iso($context); + $actual = $iso->to(null); + + static::assertSame('', $actual); + } }