Skip to content

Commit bc82599

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

21 files changed

+381
-92
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"php-soap/engine": "^2.14",
2828
"php-soap/wsdl": "^1.12",
2929
"php-soap/xml": "^1.8",
30-
"php-soap/wsdl-reader": "~0.20"
30+
"php-soap/wsdl-reader": "~0.26"
3131
},
3232
"require-dev": {
3333
"vimeo/psalm": "^5.26",

examples/encoders/simpleType/anyType-with-xsi-info.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
88
use Soap\Encoding\Encoder\XmlEncoder;
99
use Soap\Encoding\EncoderRegistry;
10-
use Soap\Encoding\Xml\Writer\ElementValueBuilder;
10+
use Soap\Encoding\Xml\Writer\XsiAttributeBuilder;
1111
use Soap\WsdlReader\Model\Definitions\BindingUse;
1212
use VeeWee\Reflecta\Iso\Iso;
1313

@@ -60,7 +60,7 @@ public function resolveXsiTypeForValue(Context $context, mixed $value): string
6060
return match (true) {
6161
$value instanceof \DateTime => 'xsd:datetime',
6262
$value instanceof \Date => 'xsd:date',
63-
default => ElementValueBuilder::resolveXsiTypeForValue($context, $value),
63+
default => XsiAttributeBuilder::resolveXsiTypeForValue($context, $value),
6464
};
6565
}
6666

@@ -72,7 +72,7 @@ public function resolveXsiTypeForValue(Context $context, mixed $value): string
7272
*/
7373
public function shouldIncludeXsiTargetNamespace(Context $context): bool
7474
{
75-
return ElementValueBuilder::shouldIncludeXsiTargetNamespace($context);
75+
return XsiAttributeBuilder::shouldIncludeXsiTargetNamespace($context);
7676
}
7777
}
7878
);

src/Encoder/EncoderDetector.php

Lines changed: 23 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,27 @@ 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+
/**
56+
* @param XmlEncoder<mixed, string> $encoder
57+
* @return XmlEncoder<mixed, string>
58+
*/
59+
private function enhanceEncoder(Context $context, XmlEncoder $encoder): XmlEncoder
60+
{
61+
$meta = $context->type->getMeta();
62+
$isSimple = $meta->isSimple()->unwrapOr(false);
63+
64+
if (!$isSimple && !$encoder instanceof Feature\DisregardXsiInformation && $context->bindingUse === BindingUse::ENCODED) {
65+
$encoder = new XsiTypeEncoder($encoder);
66+
}
4967

5068
if (!$encoder instanceof Feature\ListAware && $meta->isRepeatingElement()->unwrapOr(false)) {
5169
$encoder = new RepeatingElementEncoder($encoder);
@@ -55,9 +73,7 @@ public function __invoke(Context $context): XmlEncoder
5573
$encoder = new OptionalElementEncoder($encoder);
5674
}
5775

58-
$encoder = new ErrorHandlingEncoder($encoder);
59-
60-
return $this->cache[$type] = $encoder;
76+
return new ErrorHandlingEncoder($encoder);
6177
}
6278

6379
/**

src/Encoder/FixedIsoEncoder.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Soap\Encoding\Encoder;
4+
5+
use VeeWee\Reflecta\Iso\Iso;
6+
7+
/**
8+
* @template S
9+
* @template A
10+
* @implements XmlEncoder<S, A>
11+
*/
12+
final readonly class FixedIsoEncoder implements XmlEncoder
13+
{
14+
/**
15+
* @param Iso<S, A> $iso
16+
*/
17+
public function __construct(
18+
private Iso $iso,
19+
) {
20+
}
21+
22+
/**
23+
* @return Iso<S, A>
24+
*/
25+
public function iso(Context $context): Iso
26+
{
27+
return $this->iso;
28+
}
29+
}

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: 20 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,22 @@ 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+
/**
38+
* @param XmlEncoder<mixed, string> $encoder
39+
* @return XmlEncoder<mixed, string|null>
40+
*/
41+
private function enhanceEncoder(Context $context, XmlEncoder $encoder): XmlEncoder
2842
{
2943
$type = $context->type;
3044
$meta = $type->getMeta();
3145

32-
$encoder = $this->detectSimpleTypeEncoder($type, $context);
3346
if (!$encoder instanceof Feature\ListAware && $this->detectIsListType($type)) {
3447
$encoder = new SimpleListEncoder($encoder);
3548
}
@@ -43,6 +56,10 @@ public function __invoke(Context $context): XmlEncoder
4356
$encoder = new ElementEncoder($encoder);
4457
}
4558

59+
if (!$encoder instanceof Feature\DisregardXsiInformation && $context->bindingUse === BindingUse::ENCODED) {
60+
$encoder = new XsiTypeEncoder($encoder);
61+
}
62+
4663
if ($meta->isNullable()->unwrapOr(false) && !$encoder instanceof Feature\OptionalAware) {
4764
$encoder = new OptionalElementEncoder($encoder);
4865
}
@@ -54,8 +71,9 @@ public function __invoke(Context $context): XmlEncoder
5471
/**
5572
* @return XmlEncoder<mixed, string>
5673
*/
57-
private function detectSimpleTypeEncoder(XsdType $type, Context $context): XmlEncoder
74+
private function detectSimpleTypeEncoder(Context $context): XmlEncoder
5875
{
76+
$type = $context->type;
5977
$meta = $type->getMeta();
6078

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

0 commit comments

Comments
 (0)