Skip to content

Commit e518f39

Browse files
committed
Respect xsi:type information better
1 parent 5d8e020 commit e518f39

File tree

13 files changed

+409
-74
lines changed

13 files changed

+409
-74
lines changed

src/Encoder/EncoderDetector.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Soap\Encoding\Encoder;
55

66
use Soap\Engine\Metadata\Model\XsdType;
7+
use Soap\WsdlReader\Model\Definitions\BindingUse;
78
use stdClass;
89
use WeakMap;
910

@@ -42,10 +43,23 @@ public function __invoke(Context $context): XmlEncoder
4243

4344
$meta = $type->getMeta();
4445

45-
$encoder = match(true) {
46-
$meta->isSimple()->unwrapOr(false) => SimpleType\EncoderDetector::default()($context),
47-
default => $this->detectComplexTypeEncoder($type, $context),
48-
};
46+
return $this->cache[$type] = $this->enhanceEncoder(
47+
$context,
48+
match(true) {
49+
$meta->isSimple()->unwrapOr(false) => SimpleType\EncoderDetector::default()($context),
50+
default => $this->detectComplexTypeEncoder($type, $context)
51+
}
52+
);
53+
}
54+
55+
private function enhanceEncoder(Context $context, XmlEncoder $encoder): XmlEncoder
56+
{
57+
$meta = $context->type->getMeta();
58+
$isSimple = $meta->isSimple()->unwrapOr(false);
59+
60+
if (!$isSimple && !$encoder instanceof Feature\DisregardXsiInformation && $context->bindingUse === BindingUse::ENCODED) {
61+
$encoder = new XsiTypeEncoder($encoder);
62+
}
4963

5064
if (!$encoder instanceof Feature\ListAware && $meta->isRepeatingElement()->unwrapOr(false)) {
5165
$encoder = new RepeatingElementEncoder($encoder);
@@ -55,9 +69,7 @@ public function __invoke(Context $context): XmlEncoder
5569
$encoder = new OptionalElementEncoder($encoder);
5670
}
5771

58-
$encoder = new ErrorHandlingEncoder($encoder);
59-
60-
return $this->cache[$type] = $encoder;
72+
return new ErrorHandlingEncoder($encoder);
6173
}
6274

6375
/**

src/Encoder/FixedIsoEncoder.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Soap\Encoding\Encoder;
4+
5+
use VeeWee\Reflecta\Iso\Iso;
6+
7+
final readonly class FixedIsoEncoder implements XmlEncoder
8+
{
9+
public function __construct(
10+
private Iso $iso,
11+
) {
12+
}
13+
14+
public function iso(Context $context): Iso
15+
{
16+
return $this->iso;
17+
}
18+
}

src/Encoder/ObjectEncoder.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
use Closure;
77
use Exception;
8-
use Soap\Encoding\TypeInference\XsiTypeDetector;
98
use Soap\Encoding\Xml\Node\Element;
109
use Soap\Encoding\Xml\Reader\DocumentToLookupArrayReader;
1110
use Soap\Encoding\Xml\Writer\AttributeBuilder;
@@ -83,11 +82,12 @@ private function to(Context $context, ObjectAccess $objectAccess, object|array $
8382
$context,
8483
writeChildren(
8584
[
86-
(new XsiAttributeBuilder(
85+
XsiAttributeBuilder::forEncodedValue(
8786
$context,
88-
XsiTypeDetector::detectFromValue($context, []),
89-
includeXsiTargetNamespace: !$objectAccess->isAnyPropertyQualified,
90-
)),
87+
$this,
88+
$data,
89+
forceIncludeXsiTargetNamespace: !$objectAccess->isAnyPropertyQualified,
90+
),
9191
...map_with_key(
9292
$objectAccess->properties,
9393
static function (string $normalizePropertyName, Property $property) use ($objectAccess, $data, $defaultAction) : Closure {

src/Encoder/SimpleType/EncoderDetector.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
use Soap\Encoding\Encoder\Feature;
99
use Soap\Encoding\Encoder\OptionalElementEncoder;
1010
use Soap\Encoding\Encoder\XmlEncoder;
11+
use Soap\Encoding\Encoder\XsiTypeEncoder;
1112
use Soap\Engine\Metadata\Model\XsdType;
13+
use Soap\WsdlReader\Model\Definitions\BindingUse;
1214
use function Psl\Iter\any;
1315

1416
final class EncoderDetector
@@ -25,11 +27,18 @@ public static function default(): self
2527
* @return XmlEncoder<mixed, string|null>
2628
*/
2729
public function __invoke(Context $context): XmlEncoder
30+
{
31+
return $this->enhanceEncoder(
32+
$context,
33+
$this->detectSimpleTypeEncoder($context)
34+
);
35+
}
36+
37+
private function enhanceEncoder(Context $context, XmlEncoder $encoder): XmlEncoder
2838
{
2939
$type = $context->type;
3040
$meta = $type->getMeta();
3141

32-
$encoder = $this->detectSimpleTypeEncoder($type, $context);
3342
if (!$encoder instanceof Feature\ListAware && $this->detectIsListType($type)) {
3443
$encoder = new SimpleListEncoder($encoder);
3544
}
@@ -43,6 +52,10 @@ public function __invoke(Context $context): XmlEncoder
4352
$encoder = new ElementEncoder($encoder);
4453
}
4554

55+
if (!$encoder instanceof Feature\DisregardXsiInformation && $context->bindingUse === BindingUse::ENCODED) {
56+
$encoder = new XsiTypeEncoder($encoder);
57+
}
58+
4659
if ($meta->isNullable()->unwrapOr(false) && !$encoder instanceof Feature\OptionalAware) {
4760
$encoder = new OptionalElementEncoder($encoder);
4861
}
@@ -54,8 +67,9 @@ public function __invoke(Context $context): XmlEncoder
5467
/**
5568
* @return XmlEncoder<mixed, string>
5669
*/
57-
private function detectSimpleTypeEncoder(XsdType $type, Context $context): XmlEncoder
70+
private function detectSimpleTypeEncoder(Context $context): XmlEncoder
5871
{
72+
$type = $context->type;
5973
$meta = $type->getMeta();
6074

6175
// Try to find a direct match:

src/Encoder/XsiTypeEncoder.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Soap\Encoding\Encoder;
4+
5+
use Soap\Encoding\TypeInference\XsiTypeDetector;
6+
use Soap\Encoding\Xml\Node\Element;
7+
use VeeWee\Reflecta\Iso\Iso;
8+
9+
/**
10+
* @implements XmlEncoder<mixed, string>
11+
*/
12+
final readonly class XsiTypeEncoder implements Feature\ElementAware, XmlEncoder
13+
{
14+
/**
15+
* @param XmlEncoder<mixed, string> $encoder
16+
*/
17+
public function __construct(
18+
private XmlEncoder $encoder
19+
) {
20+
}
21+
22+
/**
23+
* @return Iso<mixed, string>
24+
*/
25+
public function iso(Context $context): Iso
26+
{
27+
return new Iso(
28+
function (mixed $value) use ($context) : string {
29+
return $this->to($context, $value);
30+
},
31+
function (string|Element $value) use ($context) : mixed {
32+
return $this->from(
33+
$context,
34+
($value instanceof Element ? $value : Element::fromString($value))
35+
);
36+
}
37+
);
38+
}
39+
40+
private function to(Context $context, mixed $value): string
41+
{
42+
// TODO : Load the correct encoder based on the value somehow.. ?
43+
// Other idea is to create some kind of extended object encoder ?
44+
45+
return $this->encoder->iso($context)->to($value);
46+
}
47+
48+
private function from(Context $context, Element $value): mixed
49+
{
50+
/** @var XmlEncoder<string, mixed> $encoder */
51+
$encoder = match (true) {
52+
$this->encoder instanceof Feature\DisregardXsiInformation => $this->encoder,
53+
default => XsiTypeDetector::detectEncoderFromXmlElement($context, $value->element())->unwrapOr($this->encoder)
54+
};
55+
56+
return $encoder->iso($context)->from($value);
57+
}
58+
}

src/EncoderRegistry.php

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use Soap\Encoding\Encoder\Context;
1010
use Soap\Encoding\Encoder\EncoderDetector;
1111
use Soap\Encoding\Encoder\ObjectEncoder;
12-
use Soap\Encoding\Encoder\OptionalElementEncoder;
1312
use Soap\Encoding\Encoder\SimpleType;
1413
use Soap\Encoding\Encoder\SoapEnc;
1514
use Soap\Encoding\Encoder\XmlEncoder;
@@ -174,7 +173,7 @@ public function addClassMap(string $namespace, string $name, string $class): sel
174173
{
175174
$this->complextTypeMap->add(
176175
(new QNameFormatter())($namespace, $name),
177-
new OptionalElementEncoder(new ObjectEncoder($class))
176+
new ObjectEncoder($class)
178177
);
179178

180179
return $this;
@@ -281,12 +280,17 @@ public function findSimpleEncoderByNamespaceName(string $namespace, string $name
281280

282281
public function hasRegisteredSimpleTypeForXsdType(XsdType $type): bool
283282
{
284-
$qNameFormatter = new QNameFormatter();
285-
286-
return $this->simpleTypeMap->contains($qNameFormatter(
283+
return $this->hasRegisteredSimpleTypeForNamespaceName(
287284
$type->getXmlNamespace(),
288285
$type->getXmlTypeName()
289-
));
286+
);
287+
}
288+
289+
public function hasRegisteredSimpleTypeForNamespaceName(string $namespace, string $name): bool
290+
{
291+
$qNameFormatter = new QNameFormatter();
292+
293+
return $this->simpleTypeMap->contains($qNameFormatter($namespace, $name));
290294
}
291295

292296
/**
@@ -312,19 +316,22 @@ public function findComplexEncoderByNamespaceName(string $namespace, string $nam
312316
return $found;
313317
}
314318

315-
return new OptionalElementEncoder(
316-
new ObjectEncoder(stdClass::class)
317-
);
319+
return new ObjectEncoder(stdClass::class);
318320
}
319321

320322
public function hasRegisteredComplexTypeForXsdType(XsdType $type): bool
321323
{
322-
$qNameFormatter = new QNameFormatter();
323-
324-
return $this->complextTypeMap->contains($qNameFormatter(
324+
return $this->hasRegisteredComplexTypeForNamespaceName(
325325
$type->getXmlNamespace(),
326326
$type->getXmlTypeName()
327-
));
327+
);
328+
}
329+
330+
public function hasRegisteredComplexTypeForNamespaceName(string $namespace, string $name): bool
331+
{
332+
$qNameFormatter = new QNameFormatter();
333+
334+
return $this->complextTypeMap->contains($qNameFormatter($namespace, $name));
328335
}
329336

330337
/**

src/TypeInference/XsiTypeDetector.php

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
use DOMElement;
77
use Psl\Option\Option;
88
use Soap\Encoding\Encoder\Context;
9+
use Soap\Encoding\Encoder\FixedIsoEncoder;
910
use Soap\Encoding\Encoder\XmlEncoder;
11+
use Soap\Engine\Metadata\Model\XsdType;
1012
use Soap\WsdlReader\Model\Definitions\BindingUse;
1113
use Soap\WsdlReader\Parser\Xml\QnameParser;
1214
use Soap\Xml\Xmlns as SoapXmlns;
@@ -42,9 +44,9 @@ static function () use ($context, $value) {
4244
}
4345

4446
/**
45-
* @return Option<XmlEncoder<mixed, string>>
47+
* @return Option<XsdType>
4648
*/
47-
public static function detectEncoderFromXmlElement(Context $context, DOMElement $element): Option
49+
public static function detectXsdTypeFromXmlElement(Context $context, DOMElement $element): Option
4850
{
4951
if ($context->bindingUse !== BindingUse::ENCODED) {
5052
return none();
@@ -70,14 +72,41 @@ public static function detectEncoderFromXmlElement(Context $context, DOMElement
7072
return none();
7173
}
7274

73-
$type = $context->type;
74-
$meta = $type->getMeta();
75+
return some(
76+
// We create a new type based on the detected xsi:type, but we keep the meta information of the original type.
77+
// This way we can still detect if the type is nullable, a union, used on an element, ...
78+
$context->type
79+
->copy($localName)
80+
->withXmlTypeName($localName)
81+
->withXmlNamespace($namespaceUri)
82+
);
83+
}
84+
85+
/**
86+
* @return Option<XmlEncoder<mixed, string>>
87+
*/
88+
public static function detectEncoderFromXmlElement(Context $context, DOMElement $element): Option
89+
{
90+
$requestedXsiType = self::detectXsdTypeFromXmlElement($context, $element);
91+
if (!$requestedXsiType->isSome()) {
92+
return none();
93+
}
94+
95+
// Enhance context to avoid duplicate optionals, repeating elements, xsi:type detections, ...
96+
$type = $requestedXsiType->unwrap();
97+
$encoderDetectorTypeMeta = $type->getMeta()
98+
->withIsNullable(false)
99+
->withIsRepeatingElement(false);
100+
$encoderDetectorContext = $context
101+
->withType($type->withMeta(static fn () => $encoderDetectorTypeMeta))
102+
->withBindingUse(BindingUse::LITERAL);
75103

76104
return some(
77-
match(true) {
78-
$meta->isSimple()->unwrapOr(false) => $context->registry->findSimpleEncoderByNamespaceName($namespaceUri, $localName),
79-
default => $context->registry->findComplexEncoderByNamespaceName($namespaceUri, $localName),
80-
}
105+
new FixedIsoEncoder(
106+
$context->registry->detectEncoderForContext($encoderDetectorContext)->iso(
107+
$context->withType($type)
108+
),
109+
)
81110
);
82111
}
83112

src/Xml/Reader/ElementValueReader.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
use DOMElement;
77
use Soap\Encoding\Encoder\Context;
8-
use Soap\Encoding\Encoder\Feature\DisregardXsiInformation;
98
use Soap\Encoding\Encoder\XmlEncoder;
10-
use Soap\Encoding\TypeInference\XsiTypeDetector;
119
use function Psl\Type\string;
1210
use function VeeWee\Xml\Dom\Locator\Node\value as readValue;
1311

@@ -22,12 +20,6 @@ public function __invoke(
2220
XmlEncoder $encoder,
2321
DOMElement $element
2422
): mixed {
25-
/** @var XmlEncoder<string, mixed> $encoder */
26-
$encoder = match (true) {
27-
$encoder instanceof DisregardXsiInformation => $encoder,
28-
default => XsiTypeDetector::detectEncoderFromXmlElement($context, $element)->unwrapOr($encoder)
29-
};
30-
3123
return $encoder->iso($context)->from(
3224
readValue($element, string())
3325
);

0 commit comments

Comments
 (0)