Skip to content

Commit b5648d4

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

16 files changed

+445
-83
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 declare(strict_types=1);
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/SoapEnc/ApacheMapEncoder.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Soap\Encoding\Encoder\Context;
99
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
1010
use Soap\Encoding\Encoder\XmlEncoder;
11-
use Soap\Encoding\TypeInference\XsiTypeDetector;
1211
use Soap\Encoding\Xml\Node\Element;
1312
use Soap\Encoding\Xml\Reader\ElementValueReader;
1413
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
@@ -58,18 +57,18 @@ private function encodeArray(Context $context, array $data): string
5857
return (new XsdTypeXmlElementWriter())(
5958
$context,
6059
buildChildren([
61-
new XsiAttributeBuilder($context, XsiTypeDetector::detectFromValue($context, $data)),
60+
new XsiAttributeBuilder($context, XsiAttributeBuilder::resolveXsiTypeForValue($context, $data)),
6261
...\Psl\Vec\map_with_key(
6362
$data,
6463
static fn (mixed $key, mixed $value): Closure => element(
6564
'item',
6665
buildChildren([
6766
element('key', buildChildren([
68-
(new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $key))),
67+
(new XsiAttributeBuilder($anyContext, XsiAttributeBuilder::resolveXsiTypeForValue($anyContext, $key))),
6968
buildValue(ScalarTypeEncoder::default()->iso($context)->to($key))
7069
])),
7170
element('value', buildChildren([
72-
(new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $value))),
71+
(new XsiAttributeBuilder($anyContext, XsiAttributeBuilder::resolveXsiTypeForValue($anyContext, $value))),
7372
buildValue(ScalarTypeEncoder::default()->iso($context)->to($value))
7473
])),
7574
]),

src/Encoder/SoapEnc/SoapArrayEncoder.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Soap\Encoding\Encoder\Context;
99
use Soap\Encoding\Encoder\Feature\ListAware;
1010
use Soap\Encoding\Encoder\XmlEncoder;
11-
use Soap\Encoding\TypeInference\XsiTypeDetector;
1211
use Soap\Encoding\Xml\Node\Element;
1312
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
1413
use Soap\Encoding\Xml\Writer\XsiAttributeBuilder;
@@ -70,7 +69,7 @@ private function encodeArray(Context $context, SoapArrayAccess $arrayAccess, arr
7069
? [
7170
new XsiAttributeBuilder(
7271
$context,
73-
XsiTypeDetector::detectFromValue($context, [])
72+
XsiAttributeBuilder::resolveXsiTypeForValue($context, [])
7473
),
7574
prefixed_attribute(
7675
'SOAP-ENC',

src/Encoder/SoapEnc/SoapObjectEncoder.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Soap\Encoding\Encoder\Context;
99
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
1010
use Soap\Encoding\Encoder\XmlEncoder;
11-
use Soap\Encoding\TypeInference\XsiTypeDetector;
1211
use Soap\Encoding\Xml\Node\Element;
1312
use Soap\Encoding\Xml\Reader\ElementValueReader;
1413
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
@@ -56,13 +55,13 @@ private function encodeArray(Context $context, object $data): string
5655
return (new XsdTypeXmlElementWriter())(
5756
$context,
5857
children([
59-
new XsiAttributeBuilder($context, XsiTypeDetector::detectFromValue($context, $data)),
58+
new XsiAttributeBuilder($context, XsiAttributeBuilder::resolveXsiTypeForValue($context, $data)),
6059
...\Psl\Vec\map_with_key(
6160
(array) $data,
6261
static fn (mixed $key, mixed $value): Closure => element(
6362
(string) $key,
6463
children([
65-
(new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $value))),
64+
(new XsiAttributeBuilder($anyContext, XsiAttributeBuilder::resolveXsiTypeForValue($anyContext, $value))),
6665
buildValue(ScalarTypeEncoder::default()->iso($context)->to($value))
6766
]),
6867
)

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
/**

0 commit comments

Comments
 (0)