Skip to content

Commit 0d1de01

Browse files
authored
Merge pull request #22 from veewee/alternate-resolveXsiType
Introduce a XsiTypeCalculator feature that can enhance xsi:type calculation from the encoder
2 parents abbf352 + a7aa57d commit 0d1de01

File tree

5 files changed

+129
-8
lines changed

5 files changed

+129
-8
lines changed

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

+30-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
use Soap\Encoding\Encoder\Context;
55
use Soap\Encoding\Encoder\Feature\ElementContextEnhancer;
6+
use Soap\Encoding\Encoder\Feature\XsiTypeCalculator;
67
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
78
use Soap\Encoding\Encoder\XmlEncoder;
89
use Soap\Encoding\EncoderRegistry;
10+
use Soap\Encoding\Xml\Writer\ElementValueBuilder;
911
use Soap\WsdlReader\Model\Definitions\BindingUse;
1012
use VeeWee\Reflecta\Iso\Iso;
1113

@@ -31,7 +33,8 @@
3133
'anyType',
3234
new class implements
3335
ElementContextEnhancer,
34-
XmlEncoder {
36+
XmlEncoder,
37+
XsiTypeCalculator {
3538
public function iso(Context $context): Iso
3639
{
3740
return (new ScalarTypeEncoder())->iso($context);
@@ -45,5 +48,31 @@ public function enhanceElementContext(Context $context): Context
4548
{
4649
return $context->withBindingUse(BindingUse::ENCODED);
4750
}
51+
52+
/**
53+
* Can be used to fine-tune the xsi:type element.
54+
* For example, xsi:type="xsd:date" when dealing with value's like `DateTimeImmutable`.
55+
*
56+
* A default fallback function is provided in the ElementValueBuilder class.
57+
*/
58+
public function resolveXsiTypeForValue(Context $context, mixed $value): string
59+
{
60+
return match (true) {
61+
$value instanceof \DateTime => 'xsd:datetime',
62+
$value instanceof \Date => 'xsd:date',
63+
default => ElementValueBuilder::resolveXsiTypeForValue($context, $value),
64+
};
65+
}
66+
67+
/**
68+
* Determines if the xmlns of the xsi:type prefix should be imported.
69+
* For example: xsd:date will import xmlns:xsd="...".
70+
*
71+
* A default fallback function is provided in the ElementValueBuilder class.
72+
*/
73+
public function shouldIncludeXsiTargetNamespace(Context $context): bool
74+
{
75+
return ElementValueBuilder::shouldIncludeXsiTargetNamespace($context);
76+
}
4877
}
4978
);

src/Encoder/Feature/DisregardXsiInformation.php

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
namespace Soap\Encoding\Encoder\Feature;
55

6+
/**
7+
* Tells the decoder to disregard any xsi:type information on the element when decoding an element.
8+
* It will use the original provided decoder by default and won't try to guess the decoder based on xsi:type.
9+
*/
610
interface DisregardXsiInformation
711
{
812
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Encoder\Feature;
5+
6+
use Soap\Encoding\Encoder\Context;
7+
8+
/**
9+
* By implementing this feature on your simpleType encoder, you can let the encoder decide what xsi:type attribute should be set to for a given value.
10+
*/
11+
interface XsiTypeCalculator
12+
{
13+
/**
14+
* @return string The value for the xsi:type attribute
15+
*
16+
* A sensible default fallback function is provided in the `ElementValueBuilder` class.
17+
*/
18+
public function resolveXsiTypeForValue(Context $context, mixed $value): string;
19+
20+
21+
/**
22+
* Tells the XsiAttributeBuilder that the prefix of the xsi:type should be imported as a xmlns namespace.
23+
*
24+
* A sensible default fallback function is provided in the `ElementValueBuilder` class.
25+
*/
26+
public function shouldIncludeXsiTargetNamespace(Context $context): bool;
27+
}

src/Xml/Writer/ElementValueBuilder.php

+35-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use Generator;
77
use Soap\Encoding\Encoder\Context;
8-
use Soap\Encoding\Encoder\Feature\CData;
8+
use Soap\Encoding\Encoder\Feature;
99
use Soap\Encoding\Encoder\XmlEncoder;
1010
use Soap\Encoding\TypeInference\XsiTypeDetector;
1111
use Soap\WsdlReader\Model\Definitions\BindingUse;
@@ -48,16 +48,45 @@ private function buildXsiType(XMLWriter $writer): Generator
4848
}
4949

5050
$context = $this->context;
51-
$type = $context->type;
51+
[$xsiType, $includeXsiTargetNamespace] = match(true) {
52+
$this->encoder instanceof Feature\XsiTypeCalculator => [
53+
$this->encoder->resolveXsiTypeForValue($context, $this->value),
54+
$this->encoder->shouldIncludeXsiTargetNamespace($context),
55+
],
56+
default => [
57+
self::resolveXsiTypeForValue($context, $this->value),
58+
self::shouldIncludeXsiTargetNamespace($context),
59+
],
60+
};
5261

5362
yield from (new XsiAttributeBuilder(
5463
$this->context,
55-
XsiTypeDetector::detectFromValue($context, $this->value),
56-
includeXsiTargetNamespace: $type->getXmlTargetNamespace() !== $type->getXmlNamespace()
57-
|| !$type->getMeta()->isQualified()->unwrapOr(false)
64+
$xsiType,
65+
$includeXsiTargetNamespace,
5866
))($writer);
5967
}
6068

69+
/**
70+
* Can be used as a default fallback function when implementing the XsiTypeCalculator interface.
71+
* Tells the XsiAttributeBuilder what xsi:type attribute should be set to for a given value.
72+
*/
73+
public static function resolveXsiTypeForValue(Context $context, mixed $value): string
74+
{
75+
return XsiTypeDetector::detectFromValue($context, $value);
76+
}
77+
78+
/**
79+
* Can be used as a default fallback function when implementing the XsiTypeCalculator interface.
80+
* Tells the XsiAttributeBuilder that the prefix of the xsi:type should be imported as a xmlns namespace.
81+
*/
82+
public static function shouldIncludeXsiTargetNamespace(Context $context): bool
83+
{
84+
$type = $context->type;
85+
86+
return $type->getXmlTargetNamespace() !== $type->getXmlNamespace()
87+
|| !$type->getMeta()->isQualified()->unwrapOr(false);
88+
}
89+
6190
/**
6291
* @return Generator<bool>
6392
*/
@@ -66,7 +95,7 @@ private function buildValue(XMLWriter $writer): Generator
6695
$encoded = $this->encoder->iso($this->context)->to($this->value);
6796

6897
$builder = match (true) {
69-
$this->encoder instanceof CData => cdata(value($encoded)),
98+
$this->encoder instanceof Feature\CData => cdata(value($encoded)),
7099
default => value($encoded)
71100
};
72101

tests/Unit/Encoder/ElementEncoderTest.php

+33-1
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
use Soap\Encoding\Encoder\Context;
88
use Soap\Encoding\Encoder\ElementEncoder;
99
use Soap\Encoding\Encoder\Feature\ElementContextEnhancer;
10+
use Soap\Encoding\Encoder\Feature\XsiTypeCalculator;
1011
use Soap\Encoding\Encoder\SimpleType\IntTypeEncoder;
1112
use Soap\Encoding\Encoder\SimpleType\StringTypeEncoder;
1213
use Soap\Encoding\Encoder\XmlEncoder;
1314
use Soap\Encoding\Xml\Node\Element;
1415
use Soap\Engine\Metadata\Model\TypeMeta;
1516
use Soap\Engine\Metadata\Model\XsdType;
17+
use Soap\WsdlReader\Model\Definitions\BindingUse;
1618
use VeeWee\Reflecta\Iso\Iso;
1719

1820
#[CoversClass(ElementEncoder::class)]
@@ -25,7 +27,11 @@ public static function provideIsomorphicCases(): iterable
2527
'context' => $context = self::createContext(
2628
$xsdType = XsdType::guess('string')
2729
->withXmlTargetNodeName('hello')
28-
->withMeta(static fn (TypeMeta $meta): TypeMeta => $meta->withIsQualified(true))
30+
->withMeta(
31+
static fn (TypeMeta $meta): TypeMeta => $meta
32+
->withIsQualified(true)
33+
->withIsSimple(true)
34+
)
2935
),
3036
];
3137

@@ -79,6 +85,32 @@ public function enhanceElementContext(Context $context): Context
7985
'xml' => '<bonjour>32</bonjour>',
8086
'data' => 32,
8187
];
88+
yield 'xsi-type-calculating-encoder' => [
89+
...$baseConfig,
90+
'encoder' => $encoder = new ElementEncoder(new class implements ElementContextEnhancer, XmlEncoder, XsiTypeCalculator {
91+
public function iso(Context $context): Iso
92+
{
93+
return (new IntTypeEncoder())->iso($context);
94+
}
95+
96+
public function enhanceElementContext(Context $context): Context
97+
{
98+
return $context->withBindingUse(BindingUse::ENCODED);
99+
}
100+
101+
public function resolveXsiTypeForValue(Context $context, mixed $value): string
102+
{
103+
return 'xsd:'.get_debug_type($value);
104+
}
105+
106+
public function shouldIncludeXsiTargetNamespace(Context $context): bool
107+
{
108+
return true;
109+
}
110+
}),
111+
'xml' => '<hello xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:int" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">32</hello>',
112+
'data' => 32,
113+
];
82114
}
83115

84116
public function test_it_can_decode_from_xml_item(): void

0 commit comments

Comments
 (0)