From 1bee864405288d9ac3af9860fc08500e84f9c20b Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Thu, 15 Oct 2026 17:39:43 +0200 Subject: [PATCH 1/2] Skip xsi:type detection for elements without attributes An xsi:type can only live on the element itself, so an element without attributes always decodes through the inner encoder. Checking hasAttributes() first avoids the two attribute lookups and the Option wrapping for every such element on decode. --- src/Encoder/XsiTypeEncoder.php | 2 + tests/Unit/Encoder/XsiTypeEncoderTest.php | 54 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/Unit/Encoder/XsiTypeEncoderTest.php diff --git a/src/Encoder/XsiTypeEncoder.php b/src/Encoder/XsiTypeEncoder.php index 1f92fb1..64e90c0 100644 --- a/src/Encoder/XsiTypeEncoder.php +++ b/src/Encoder/XsiTypeEncoder.php @@ -63,8 +63,10 @@ private function to(IsoInterface $innerIso, mixed $value): string */ private function from(Context $context, IsoInterface $innerIso, Element $value): mixed { + // xsi:type is an attribute on the element itself, so an element without attributes cannot carry one. $iso = match (true) { $this->encoder instanceof Feature\DisregardXsiInformation => $innerIso, + !$value->element()->hasAttributes() => $innerIso, default => XsiTypeDetector::detectEncoderFromXmlElement($context, $value->element()) ->map(static fn (XmlEncoder $encoder): IsoInterface => $encoder->iso($context)) ->unwrapOr($innerIso), diff --git a/tests/Unit/Encoder/XsiTypeEncoderTest.php b/tests/Unit/Encoder/XsiTypeEncoderTest.php new file mode 100644 index 0000000..c780ef0 --- /dev/null +++ b/tests/Unit/Encoder/XsiTypeEncoderTest.php @@ -0,0 +1,54 @@ +decode( + '32' + ); + + static::assertSame(32, $actual); + } + + public function test_it_decodes_an_element_without_attributes_through_the_inner_encoder(): void + { + $actual = $this->decode('32'); + + static::assertSame('32', $actual); + } + + private function decode(string $xml): mixed + { + $encoder = new XsiTypeEncoder(new ElementEncoder(new StringTypeEncoder())); + + return $encoder->iso($this->createStringContext())->from(Element::fromString($xml)); + } + + private function createStringContext(): Context + { + return self::createContext( + XsdType::guess('string') + ->withXmlNamespace('http://www.w3.org/2001/XMLSchema') + ->withXmlTargetNodeName('hello') + ->withMeta(static fn (TypeMeta $meta): TypeMeta => $meta->withIsQualified(true)->withIsSimple(true)->withIsElement(true)) + ); + } +} From b94731aea2f23ecbbf9c07a0d2843a7d38b96188 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Thu, 15 Oct 2026 17:41:10 +0200 Subject: [PATCH 2/2] Pin that a namespace declaration alone still decodes through the inner encoder --- tests/Unit/Encoder/XsiTypeEncoderTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Unit/Encoder/XsiTypeEncoderTest.php b/tests/Unit/Encoder/XsiTypeEncoderTest.php index c780ef0..dcb25ad 100644 --- a/tests/Unit/Encoder/XsiTypeEncoderTest.php +++ b/tests/Unit/Encoder/XsiTypeEncoderTest.php @@ -35,6 +35,13 @@ public function test_it_decodes_an_element_without_attributes_through_the_inner_ static::assertSame('32', $actual); } + public function test_it_decodes_an_element_with_attributes_but_no_xsi_type_through_the_inner_encoder(): void + { + $actual = $this->decode('32'); + + static::assertSame('32', $actual); + } + private function decode(string $xml): mixed { $encoder = new XsiTypeEncoder(new ElementEncoder(new StringTypeEncoder()));