diff --git a/composer.json b/composer.json
index da6a2de..957a1a3 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
"php-soap/engine": "^2.14",
"php-soap/wsdl": "^1.12",
"php-soap/xml": "^1.8",
- "php-soap/wsdl-reader": "~0.20"
+ "php-soap/wsdl-reader": "~0.26"
},
"require-dev": {
"vimeo/psalm": "^5.26",
diff --git a/examples/encoders/complexType/matching-value.php b/examples/encoders/complexType/matching-value.php
new file mode 100644
index 0000000..93ee704
--- /dev/null
+++ b/examples/encoders/complexType/matching-value.php
@@ -0,0 +1,87 @@
+
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * The result looks like this:
+ *
+ *
+ *
+ * abc
+ *
+ *
+ * def
+ * ghi
+ *
+ *
+ *
+ * <=>
+ *
+ * ^ {#2507
+ * +"responses": array:2 [
+ * 0 => A^ {#2501
+ * +foo: "abc"
+ * }
+ * 1 => B {#2504
+ * +foo: "def"
+ * +bar: "ghi"
+ * }
+ * ]
+ * }
+ */
+
+EncoderRegistry::default()
+ ->addClassMap('http://test-uri/', 'B', ImpliedSchema015B::class)
+ ->addComplexTypeConverter('http://test-uri/', 'A', new Encoder\MatchingValueEncoder(
+ encoderDetector: static fn (Encoder\Context $context, mixed $value): array =>
+ $value instanceof ImpliedSchema015B
+ ? [
+ $context->withType($context->type->copy('B')->withXmlTypeName('B')),
+ new Encoder\ObjectEncoder(ImpliedSchema015B::class),
+ ]
+ : [$context],
+ defaultEncoder: new Encoder\ObjectEncoder(ImpliedSchema015A::class)
+ ))
+ // Alternative for using stdObjects only:
+ ->addComplexTypeConverter('http://test-uri/', 'A', new Encoder\MatchingValueEncoder(
+ encoderDetector: static fn (Encoder\Context $context, mixed $value): Encoder\Context => $context->withType(
+ property_exists($value, 'bar')
+ ? $context->type->copy('B')->withXmlTypeName('B')
+ : $context->type
+ ),
+ defaultEncoder: new Encoder\ObjectEncoder(stdClass::class)
+ ));
diff --git a/examples/encoders/simpleType/anyType-with-xsi-info.php b/examples/encoders/simpleType/anyType-with-xsi-info.php
index 53947ed..392ae9a 100644
--- a/examples/encoders/simpleType/anyType-with-xsi-info.php
+++ b/examples/encoders/simpleType/anyType-with-xsi-info.php
@@ -7,7 +7,7 @@
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\EncoderRegistry;
-use Soap\Encoding\Xml\Writer\ElementValueBuilder;
+use Soap\Encoding\Xml\Writer\XsiAttributeBuilder;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use VeeWee\Reflecta\Iso\Iso;
@@ -60,7 +60,7 @@ public function resolveXsiTypeForValue(Context $context, mixed $value): string
return match (true) {
$value instanceof \DateTime => 'xsd:datetime',
$value instanceof \Date => 'xsd:date',
- default => ElementValueBuilder::resolveXsiTypeForValue($context, $value),
+ default => XsiAttributeBuilder::resolveXsiTypeForValue($context, $value),
};
}
@@ -72,7 +72,7 @@ public function resolveXsiTypeForValue(Context $context, mixed $value): string
*/
public function shouldIncludeXsiTargetNamespace(Context $context): bool
{
- return ElementValueBuilder::shouldIncludeXsiTargetNamespace($context);
+ return XsiAttributeBuilder::shouldIncludeXsiTargetNamespace($context);
}
}
);
diff --git a/src/Encoder/EncoderDetector.php b/src/Encoder/EncoderDetector.php
index 2bab86c..1c81c12 100644
--- a/src/Encoder/EncoderDetector.php
+++ b/src/Encoder/EncoderDetector.php
@@ -4,6 +4,7 @@
namespace Soap\Encoding\Encoder;
use Soap\Engine\Metadata\Model\XsdType;
+use Soap\WsdlReader\Model\Definitions\BindingUse;
use stdClass;
use WeakMap;
@@ -42,10 +43,27 @@ public function __invoke(Context $context): XmlEncoder
$meta = $type->getMeta();
- $encoder = match(true) {
- $meta->isSimple()->unwrapOr(false) => SimpleType\EncoderDetector::default()($context),
- default => $this->detectComplexTypeEncoder($type, $context),
- };
+ return $this->cache[$type] = $this->enhanceEncoder(
+ $context,
+ match(true) {
+ $meta->isSimple()->unwrapOr(false) => SimpleType\EncoderDetector::default()($context),
+ default => $this->detectComplexTypeEncoder($type, $context)
+ }
+ );
+ }
+
+ /**
+ * @param XmlEncoder $encoder
+ * @return XmlEncoder
+ */
+ private function enhanceEncoder(Context $context, XmlEncoder $encoder): XmlEncoder
+ {
+ $meta = $context->type->getMeta();
+ $isSimple = $meta->isSimple()->unwrapOr(false);
+
+ if (!$isSimple && !$encoder instanceof Feature\DisregardXsiInformation && $context->bindingUse === BindingUse::ENCODED) {
+ $encoder = new XsiTypeEncoder($encoder);
+ }
if (!$encoder instanceof Feature\ListAware && $meta->isRepeatingElement()->unwrapOr(false)) {
$encoder = new RepeatingElementEncoder($encoder);
@@ -55,9 +73,7 @@ public function __invoke(Context $context): XmlEncoder
$encoder = new OptionalElementEncoder($encoder);
}
- $encoder = new ErrorHandlingEncoder($encoder);
-
- return $this->cache[$type] = $encoder;
+ return new ErrorHandlingEncoder($encoder);
}
/**
diff --git a/src/Encoder/FixedIsoEncoder.php b/src/Encoder/FixedIsoEncoder.php
new file mode 100644
index 0000000..f41dc04
--- /dev/null
+++ b/src/Encoder/FixedIsoEncoder.php
@@ -0,0 +1,29 @@
+
+ */
+final readonly class FixedIsoEncoder implements XmlEncoder
+{
+ /**
+ * @param Iso $iso
+ */
+ public function __construct(
+ private Iso $iso,
+ ) {
+ }
+
+ /**
+ * @return Iso
+ */
+ public function iso(Context $context): Iso
+ {
+ return $this->iso;
+ }
+}
diff --git a/src/Encoder/MatchingValueEncoder.php b/src/Encoder/MatchingValueEncoder.php
new file mode 100644
index 0000000..ec98999
--- /dev/null
+++ b/src/Encoder/MatchingValueEncoder.php
@@ -0,0 +1,62 @@
+|null}
+ * @psalm-type MatchingEncoderDetector = \Closure(Context, mixed): MatchedEncoderInfo
+ *
+ * @psalm-suppress UnusedClass
+ *
+ * @implements XmlEncoder
+ */
+final readonly class MatchingValueEncoder implements XmlEncoder
+{
+ /**
+ * @param MatchingEncoderDetector $encoderDetector
+ * @param XmlEncoder $defaultEncoder
+ */
+ public function __construct(
+ private Closure $encoderDetector,
+ private XmlEncoder $defaultEncoder,
+ ) {
+ }
+
+ public function iso(Context $context): Iso
+ {
+ /** @var Iso $defaultIso */
+ $defaultIso = $this->defaultEncoder->iso($context);
+
+ return new Iso(
+ to: fn (mixed $value): string => $this->to($context, $value),
+ /**
+ * @param string|Element $value
+ */
+ from: static fn (string|Element $value): mixed => $defaultIso->from($value),
+ );
+ }
+
+ private function to(Context $context, mixed $value): string
+ {
+ $matchedEncoderInfo = ($this->encoderDetector)($context, $value);
+ [$context, $encoder] = match(true) {
+ $matchedEncoderInfo instanceof Context => [$matchedEncoderInfo, $this->defaultEncoder],
+ default => [$matchedEncoderInfo[0], $matchedEncoderInfo[1] ?? $this->defaultEncoder],
+ };
+
+ /** @psalm-suppress RedundantConditionGivenDocblockType - This gives better feedback to people using this encoder */
+ // Ensure that the encoderDetector returns valid data.
+ invariant($context instanceof Context, 'The MatchingValueEncoder::$encoderDetector callable must return a Context or an array with a Context as first element.');
+ invariant($encoder instanceof XmlEncoder, 'The MatchingValueEncoder::$encoderDetector callable must return a Context or an array with a Context as first element and an optional XmlEncoder as second element.');
+
+ return $encoder->iso($context)->to($value);
+ }
+}
diff --git a/src/Encoder/ObjectEncoder.php b/src/Encoder/ObjectEncoder.php
index 33304a4..995ebe5 100644
--- a/src/Encoder/ObjectEncoder.php
+++ b/src/Encoder/ObjectEncoder.php
@@ -5,7 +5,6 @@
use Closure;
use Exception;
-use Soap\Encoding\TypeInference\XsiTypeDetector;
use Soap\Encoding\Xml\Node\Element;
use Soap\Encoding\Xml\Reader\DocumentToLookupArrayReader;
use Soap\Encoding\Xml\Writer\AttributeBuilder;
@@ -83,11 +82,12 @@ private function to(Context $context, ObjectAccess $objectAccess, object|array $
$context,
writeChildren(
[
- (new XsiAttributeBuilder(
+ XsiAttributeBuilder::forEncodedValue(
$context,
- XsiTypeDetector::detectFromValue($context, []),
- includeXsiTargetNamespace: !$objectAccess->isAnyPropertyQualified,
- )),
+ $this,
+ $data,
+ forceIncludeXsiTargetNamespace: !$objectAccess->isAnyPropertyQualified,
+ ),
...map_with_key(
$objectAccess->properties,
static function (string $normalizePropertyName, Property $property) use ($objectAccess, $data, $defaultAction) : Closure {
diff --git a/src/Encoder/SimpleType/EncoderDetector.php b/src/Encoder/SimpleType/EncoderDetector.php
index 2c12423..74eecf3 100644
--- a/src/Encoder/SimpleType/EncoderDetector.php
+++ b/src/Encoder/SimpleType/EncoderDetector.php
@@ -8,7 +8,9 @@
use Soap\Encoding\Encoder\Feature;
use Soap\Encoding\Encoder\OptionalElementEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
+use Soap\Encoding\Encoder\XsiTypeEncoder;
use Soap\Engine\Metadata\Model\XsdType;
+use Soap\WsdlReader\Model\Definitions\BindingUse;
use function Psl\Iter\any;
final class EncoderDetector
@@ -25,11 +27,22 @@ public static function default(): self
* @return XmlEncoder
*/
public function __invoke(Context $context): XmlEncoder
+ {
+ return $this->enhanceEncoder(
+ $context,
+ $this->detectSimpleTypeEncoder($context)
+ );
+ }
+
+ /**
+ * @param XmlEncoder $encoder
+ * @return XmlEncoder
+ */
+ private function enhanceEncoder(Context $context, XmlEncoder $encoder): XmlEncoder
{
$type = $context->type;
$meta = $type->getMeta();
- $encoder = $this->detectSimpleTypeEncoder($type, $context);
if (!$encoder instanceof Feature\ListAware && $this->detectIsListType($type)) {
$encoder = new SimpleListEncoder($encoder);
}
@@ -43,6 +56,10 @@ public function __invoke(Context $context): XmlEncoder
$encoder = new ElementEncoder($encoder);
}
+ if (!$encoder instanceof Feature\DisregardXsiInformation && $context->bindingUse === BindingUse::ENCODED) {
+ $encoder = new XsiTypeEncoder($encoder);
+ }
+
if ($meta->isNullable()->unwrapOr(false) && !$encoder instanceof Feature\OptionalAware) {
$encoder = new OptionalElementEncoder($encoder);
}
@@ -54,8 +71,9 @@ public function __invoke(Context $context): XmlEncoder
/**
* @return XmlEncoder
*/
- private function detectSimpleTypeEncoder(XsdType $type, Context $context): XmlEncoder
+ private function detectSimpleTypeEncoder(Context $context): XmlEncoder
{
+ $type = $context->type;
$meta = $type->getMeta();
// Try to find a direct match:
diff --git a/src/Encoder/SoapEnc/ApacheMapEncoder.php b/src/Encoder/SoapEnc/ApacheMapEncoder.php
index 776eae4..3ed1e76 100644
--- a/src/Encoder/SoapEnc/ApacheMapEncoder.php
+++ b/src/Encoder/SoapEnc/ApacheMapEncoder.php
@@ -8,7 +8,6 @@
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
-use Soap\Encoding\TypeInference\XsiTypeDetector;
use Soap\Encoding\Xml\Node\Element;
use Soap\Encoding\Xml\Reader\ElementValueReader;
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
@@ -58,18 +57,18 @@ private function encodeArray(Context $context, array $data): string
return (new XsdTypeXmlElementWriter())(
$context,
buildChildren([
- new XsiAttributeBuilder($context, XsiTypeDetector::detectFromValue($context, $data)),
+ new XsiAttributeBuilder($context, XsiAttributeBuilder::resolveXsiTypeForValue($context, $data)),
...\Psl\Vec\map_with_key(
$data,
static fn (mixed $key, mixed $value): Closure => element(
'item',
buildChildren([
element('key', buildChildren([
- (new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $key))),
+ (new XsiAttributeBuilder($anyContext, XsiAttributeBuilder::resolveXsiTypeForValue($anyContext, $key))),
buildValue(ScalarTypeEncoder::default()->iso($context)->to($key))
])),
element('value', buildChildren([
- (new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $value))),
+ (new XsiAttributeBuilder($anyContext, XsiAttributeBuilder::resolveXsiTypeForValue($anyContext, $value))),
buildValue(ScalarTypeEncoder::default()->iso($context)->to($value))
])),
]),
diff --git a/src/Encoder/SoapEnc/SoapArrayEncoder.php b/src/Encoder/SoapEnc/SoapArrayEncoder.php
index fa4fe67..c002992 100644
--- a/src/Encoder/SoapEnc/SoapArrayEncoder.php
+++ b/src/Encoder/SoapEnc/SoapArrayEncoder.php
@@ -8,7 +8,6 @@
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\Feature\ListAware;
use Soap\Encoding\Encoder\XmlEncoder;
-use Soap\Encoding\TypeInference\XsiTypeDetector;
use Soap\Encoding\Xml\Node\Element;
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
use Soap\Encoding\Xml\Writer\XsiAttributeBuilder;
@@ -70,7 +69,7 @@ private function encodeArray(Context $context, SoapArrayAccess $arrayAccess, arr
? [
new XsiAttributeBuilder(
$context,
- XsiTypeDetector::detectFromValue($context, [])
+ XsiAttributeBuilder::resolveXsiTypeForValue($context, [])
),
prefixed_attribute(
'SOAP-ENC',
diff --git a/src/Encoder/SoapEnc/SoapObjectEncoder.php b/src/Encoder/SoapEnc/SoapObjectEncoder.php
index 92cceb2..f617823 100644
--- a/src/Encoder/SoapEnc/SoapObjectEncoder.php
+++ b/src/Encoder/SoapEnc/SoapObjectEncoder.php
@@ -8,7 +8,6 @@
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
-use Soap\Encoding\TypeInference\XsiTypeDetector;
use Soap\Encoding\Xml\Node\Element;
use Soap\Encoding\Xml\Reader\ElementValueReader;
use Soap\Encoding\Xml\Writer\XsdTypeXmlElementWriter;
@@ -56,13 +55,13 @@ private function encodeArray(Context $context, object $data): string
return (new XsdTypeXmlElementWriter())(
$context,
children([
- new XsiAttributeBuilder($context, XsiTypeDetector::detectFromValue($context, $data)),
+ new XsiAttributeBuilder($context, XsiAttributeBuilder::resolveXsiTypeForValue($context, $data)),
...\Psl\Vec\map_with_key(
(array) $data,
static fn (mixed $key, mixed $value): Closure => element(
(string) $key,
children([
- (new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $value))),
+ (new XsiAttributeBuilder($anyContext, XsiAttributeBuilder::resolveXsiTypeForValue($anyContext, $value))),
buildValue(ScalarTypeEncoder::default()->iso($context)->to($value))
]),
)
diff --git a/src/Encoder/XsiTypeEncoder.php b/src/Encoder/XsiTypeEncoder.php
new file mode 100644
index 0000000..b2d2954
--- /dev/null
+++ b/src/Encoder/XsiTypeEncoder.php
@@ -0,0 +1,59 @@
+
+ */
+final readonly class XsiTypeEncoder implements Feature\ElementAware, XmlEncoder
+{
+ /**
+ * @param XmlEncoder $encoder
+ */
+ public function __construct(
+ private XmlEncoder $encoder
+ ) {
+ }
+
+ /**
+ * @return Iso
+ */
+ public function iso(Context $context): Iso
+ {
+ return new Iso(
+ function (mixed $value) use ($context) : string {
+ return $this->to($context, $value);
+ },
+ function (string|Element $value) use ($context) : mixed {
+ return $this->from(
+ $context,
+ ($value instanceof Element ? $value : Element::fromString(non_empty_string()->assert($value)))
+ );
+ }
+ );
+ }
+
+ private function to(Context $context, mixed $value): string
+ {
+ // There is no way to know what xsi:type to use when encoding any type.
+ // The type defined in the wsdl will always be used to encode the value.
+ // If you want more control over the encoded type, please control how to encode by using the MatchingValueEncoder.
+ return $this->encoder->iso($context)->to($value);
+ }
+
+ private function from(Context $context, Element $value): mixed
+ {
+ /** @var XmlEncoder $encoder */
+ $encoder = match (true) {
+ $this->encoder instanceof Feature\DisregardXsiInformation => $this->encoder,
+ default => XsiTypeDetector::detectEncoderFromXmlElement($context, $value->element())->unwrapOr($this->encoder)
+ };
+
+ return $encoder->iso($context)->from($value);
+ }
+}
diff --git a/src/EncoderRegistry.php b/src/EncoderRegistry.php
index aef4c87..fcc921e 100644
--- a/src/EncoderRegistry.php
+++ b/src/EncoderRegistry.php
@@ -9,7 +9,6 @@
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\EncoderDetector;
use Soap\Encoding\Encoder\ObjectEncoder;
-use Soap\Encoding\Encoder\OptionalElementEncoder;
use Soap\Encoding\Encoder\SimpleType;
use Soap\Encoding\Encoder\SoapEnc;
use Soap\Encoding\Encoder\XmlEncoder;
@@ -174,7 +173,7 @@ public function addClassMap(string $namespace, string $name, string $class): sel
{
$this->complextTypeMap->add(
(new QNameFormatter())($namespace, $name),
- new OptionalElementEncoder(new ObjectEncoder($class))
+ new ObjectEncoder($class)
);
return $this;
@@ -281,12 +280,17 @@ public function findSimpleEncoderByNamespaceName(string $namespace, string $name
public function hasRegisteredSimpleTypeForXsdType(XsdType $type): bool
{
- $qNameFormatter = new QNameFormatter();
-
- return $this->simpleTypeMap->contains($qNameFormatter(
+ return $this->hasRegisteredSimpleTypeForNamespaceName(
$type->getXmlNamespace(),
$type->getXmlTypeName()
- ));
+ );
+ }
+
+ public function hasRegisteredSimpleTypeForNamespaceName(string $namespace, string $name): bool
+ {
+ $qNameFormatter = new QNameFormatter();
+
+ return $this->simpleTypeMap->contains($qNameFormatter($namespace, $name));
}
/**
@@ -312,19 +316,22 @@ public function findComplexEncoderByNamespaceName(string $namespace, string $nam
return $found;
}
- return new OptionalElementEncoder(
- new ObjectEncoder(stdClass::class)
- );
+ return new ObjectEncoder(stdClass::class);
}
public function hasRegisteredComplexTypeForXsdType(XsdType $type): bool
{
- $qNameFormatter = new QNameFormatter();
-
- return $this->complextTypeMap->contains($qNameFormatter(
+ return $this->hasRegisteredComplexTypeForNamespaceName(
$type->getXmlNamespace(),
$type->getXmlTypeName()
- ));
+ );
+ }
+
+ public function hasRegisteredComplexTypeForNamespaceName(string $namespace, string $name): bool
+ {
+ $qNameFormatter = new QNameFormatter();
+
+ return $this->complextTypeMap->contains($qNameFormatter($namespace, $name));
}
/**
diff --git a/src/TypeInference/XsiTypeDetector.php b/src/TypeInference/XsiTypeDetector.php
index 41abcee..d5be07f 100644
--- a/src/TypeInference/XsiTypeDetector.php
+++ b/src/TypeInference/XsiTypeDetector.php
@@ -6,7 +6,8 @@
use DOMElement;
use Psl\Option\Option;
use Soap\Encoding\Encoder\Context;
-use Soap\Encoding\Encoder\XmlEncoder;
+use Soap\Encoding\Encoder\FixedIsoEncoder;
+use Soap\Engine\Metadata\Model\XsdType;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use Soap\WsdlReader\Parser\Xml\QnameParser;
use Soap\Xml\Xmlns as SoapXmlns;
@@ -42,9 +43,9 @@ static function () use ($context, $value) {
}
/**
- * @return Option>
+ * @return Option
*/
- public static function detectEncoderFromXmlElement(Context $context, DOMElement $element): Option
+ public static function detectXsdTypeFromXmlElement(Context $context, DOMElement $element): Option
{
if ($context->bindingUse !== BindingUse::ENCODED) {
return none();
@@ -70,14 +71,41 @@ public static function detectEncoderFromXmlElement(Context $context, DOMElement
return none();
}
- $type = $context->type;
- $meta = $type->getMeta();
+ return some(
+ // We create a new type based on the detected xsi:type, but we keep the meta information of the original type.
+ // This way we can still detect if the type is nullable, a union, used on an element, ...
+ $context->type
+ ->copy($localName)
+ ->withXmlTypeName($localName)
+ ->withXmlNamespace($namespaceUri)
+ );
+ }
+
+ /**
+ * @return Option>
+ */
+ public static function detectEncoderFromXmlElement(Context $context, DOMElement $element): Option
+ {
+ $requestedXsiType = self::detectXsdTypeFromXmlElement($context, $element);
+ if (!$requestedXsiType->isSome()) {
+ return none();
+ }
+
+ // Enhance context to avoid duplicate optionals, repeating elements, xsi:type detections, ...
+ $type = $requestedXsiType->unwrap();
+ $encoderDetectorTypeMeta = $type->getMeta()
+ ->withIsNullable(false)
+ ->withIsRepeatingElement(false);
+ $encoderDetectorContext = $context
+ ->withType($type->withMeta(static fn () => $encoderDetectorTypeMeta))
+ ->withBindingUse(BindingUse::LITERAL);
return some(
- match(true) {
- $meta->isSimple()->unwrapOr(false) => $context->registry->findSimpleEncoderByNamespaceName($namespaceUri, $localName),
- default => $context->registry->findComplexEncoderByNamespaceName($namespaceUri, $localName),
- }
+ new FixedIsoEncoder(
+ $context->registry->detectEncoderForContext($encoderDetectorContext)->iso(
+ $context->withType($type)
+ ),
+ )
);
}
diff --git a/src/Xml/Reader/ElementValueReader.php b/src/Xml/Reader/ElementValueReader.php
index 21b244e..0ceb8e8 100644
--- a/src/Xml/Reader/ElementValueReader.php
+++ b/src/Xml/Reader/ElementValueReader.php
@@ -5,9 +5,7 @@
use DOMElement;
use Soap\Encoding\Encoder\Context;
-use Soap\Encoding\Encoder\Feature\DisregardXsiInformation;
use Soap\Encoding\Encoder\XmlEncoder;
-use Soap\Encoding\TypeInference\XsiTypeDetector;
use function Psl\Type\string;
use function VeeWee\Xml\Dom\Locator\Node\value as readValue;
@@ -22,12 +20,6 @@ public function __invoke(
XmlEncoder $encoder,
DOMElement $element
): mixed {
- /** @var XmlEncoder $encoder */
- $encoder = match (true) {
- $encoder instanceof DisregardXsiInformation => $encoder,
- default => XsiTypeDetector::detectEncoderFromXmlElement($context, $element)->unwrapOr($encoder)
- };
-
return $encoder->iso($context)->from(
readValue($element, string())
);
diff --git a/src/Xml/Writer/ElementValueBuilder.php b/src/Xml/Writer/ElementValueBuilder.php
index 0b9522e..65a197b 100644
--- a/src/Xml/Writer/ElementValueBuilder.php
+++ b/src/Xml/Writer/ElementValueBuilder.php
@@ -7,8 +7,6 @@
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\Feature;
use Soap\Encoding\Encoder\XmlEncoder;
-use Soap\Encoding\TypeInference\XsiTypeDetector;
-use Soap\WsdlReader\Model\Definitions\BindingUse;
use XMLWriter;
use function VeeWee\Xml\Writer\Builder\cdata;
use function VeeWee\Xml\Writer\Builder\children;
@@ -43,48 +41,27 @@ public function __invoke(XMLWriter $writer): Generator
*/
private function buildXsiType(XMLWriter $writer): Generator
{
- if ($this->context->bindingUse !== BindingUse::ENCODED) {
- return;
- }
-
- $context = $this->context;
- [$xsiType, $includeXsiTargetNamespace] = match(true) {
- $this->encoder instanceof Feature\XsiTypeCalculator => [
- $this->encoder->resolveXsiTypeForValue($context, $this->value),
- $this->encoder->shouldIncludeXsiTargetNamespace($context),
- ],
- default => [
- self::resolveXsiTypeForValue($context, $this->value),
- self::shouldIncludeXsiTargetNamespace($context),
- ],
- };
-
- yield from (new XsiAttributeBuilder(
+ yield from XsiAttributeBuilder::forEncodedValue(
$this->context,
- $xsiType,
- $includeXsiTargetNamespace,
- ))($writer);
+ $this->encoder,
+ $this->value,
+ )($writer);
}
/**
- * Can be used as a default fallback function when implementing the XsiTypeCalculator interface.
- * Tells the XsiAttributeBuilder what xsi:type attribute should be set to for a given value.
+ * @deprecated Use XsiAttributeBuilder::resolveXsiTypeForValue() instead. Will be removed in 1.0.0.
*/
public static function resolveXsiTypeForValue(Context $context, mixed $value): string
{
- return XsiTypeDetector::detectFromValue($context, $value);
+ return XsiAttributeBuilder::resolveXsiTypeForValue($context, $value);
}
/**
- * Can be used as a default fallback function when implementing the XsiTypeCalculator interface.
- * Tells the XsiAttributeBuilder that the prefix of the xsi:type should be imported as a xmlns namespace.
+ * @deprecated Use XsiAttributeBuilder::shouldIncludeXsiTargetNamespace() instead. Will be removed in 1.0.0.
*/
public static function shouldIncludeXsiTargetNamespace(Context $context): bool
{
- $type = $context->type;
-
- return $type->getXmlTargetNamespace() !== $type->getXmlNamespace()
- || !$type->getMeta()->isQualified()->unwrapOr(false);
+ return XsiAttributeBuilder::shouldIncludeXsiTargetNamespace($context);
}
/**
diff --git a/src/Xml/Writer/XsiAttributeBuilder.php b/src/Xml/Writer/XsiAttributeBuilder.php
index 6da83ea..1d62a41 100644
--- a/src/Xml/Writer/XsiAttributeBuilder.php
+++ b/src/Xml/Writer/XsiAttributeBuilder.php
@@ -3,12 +3,17 @@
namespace Soap\Encoding\Xml\Writer;
+use Closure;
use Generator;
use Soap\Encoding\Encoder\Context;
+use Soap\Encoding\Encoder\Feature;
+use Soap\Encoding\Encoder\XmlEncoder;
+use Soap\Encoding\TypeInference\XsiTypeDetector;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use Soap\WsdlReader\Parser\Xml\QnameParser;
use VeeWee\Xml\Xmlns\Xmlns;
use XMLWriter;
+use function VeeWee\Xml\Writer\Builder\children;
use function VeeWee\Xml\Writer\Builder\namespace_attribute;
use function VeeWee\Xml\Writer\Builder\namespaced_attribute;
@@ -21,6 +26,58 @@ public function __construct(
) {
}
+ /**
+ * @return Closure(XMLWriter): Generator
+ */
+ public static function forEncodedValue(
+ Context $context,
+ XmlEncoder $encoder,
+ mixed $value,
+ ?bool $forceIncludeXsiTargetNamespace = null,
+ ): Closure {
+ if ($context->bindingUse !== BindingUse::ENCODED) {
+ return children([]);
+ }
+
+ [$xsiType, $includeXsiTargetNamespace] = match(true) {
+ $encoder instanceof Feature\XsiTypeCalculator => [
+ $encoder->resolveXsiTypeForValue($context, $value),
+ $forceIncludeXsiTargetNamespace ?? $encoder->shouldIncludeXsiTargetNamespace($context),
+ ],
+ default => [
+ self::resolveXsiTypeForValue($context, $value),
+ $forceIncludeXsiTargetNamespace ?? self::shouldIncludeXsiTargetNamespace($context),
+ ],
+ };
+
+ return (new self(
+ $context,
+ $xsiType,
+ $includeXsiTargetNamespace,
+ ))(...);
+ }
+
+ /**
+ * Can be used as a default fallback function when implementing the XsiTypeCalculator interface.
+ * Tells the XsiAttributeBuilder what xsi:type attribute should be set to for a given value.
+ */
+ public static function resolveXsiTypeForValue(Context $context, mixed $value): string
+ {
+ return XsiTypeDetector::detectFromValue($context, $value);
+ }
+
+ /**
+ * Can be used as a default fallback function when implementing the XsiTypeCalculator interface.
+ * Tells the XsiAttributeBuilder that the prefix of the xsi:type should be imported as a xmlns namespace.
+ */
+ public static function shouldIncludeXsiTargetNamespace(Context $context): bool
+ {
+ $type = $context->type;
+
+ return $type->getXmlTargetNamespace() !== $type->getXmlNamespace()
+ || !$type->getMeta()->isQualified()->unwrapOr(false);
+ }
+
/**
* @return Generator
*/
diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema009Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema009Test.php
index 85a5c33..38ce0fe 100644
--- a/tests/PhpCompatibility/Implied/ImpliedSchema009Test.php
+++ b/tests/PhpCompatibility/Implied/ImpliedSchema009Test.php
@@ -50,7 +50,7 @@ protected function expectXml(): string
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
-
+
0
diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema010Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema010Test.php
index 87b5e9c..eda0ac8 100644
--- a/tests/PhpCompatibility/Implied/ImpliedSchema010Test.php
+++ b/tests/PhpCompatibility/Implied/ImpliedSchema010Test.php
@@ -46,7 +46,7 @@ protected function expectXml(): string
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
-
+
diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema011Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema011Test.php
index 0d13376..703136d 100644
--- a/tests/PhpCompatibility/Implied/ImpliedSchema011Test.php
+++ b/tests/PhpCompatibility/Implied/ImpliedSchema011Test.php
@@ -46,7 +46,7 @@ protected function expectXml(): string
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
-
+
diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema012Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema012Test.php
index 1813457..b7a7124 100644
--- a/tests/PhpCompatibility/Implied/ImpliedSchema012Test.php
+++ b/tests/PhpCompatibility/Implied/ImpliedSchema012Test.php
@@ -49,7 +49,7 @@ protected function expectXml(): string
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
-
+
0
diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema014Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema014Test.php
new file mode 100644
index 0000000..f8a9a1c
--- /dev/null
+++ b/tests/PhpCompatibility/Implied/ImpliedSchema014Test.php
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOXML;
+ protected string $type = 'type="tns:return"';
+
+ protected function calculateParam(): mixed
+ {
+ return (object)[
+ 'responses' => [
+ (object)['foo' => 'abc'],
+ (object)['foo' => 'def', 'bar' => 'ghi'],
+ ],
+ ];
+ }
+
+ protected function registry(): EncoderRegistry
+ {
+ return parent::registry()
+ ->addComplexTypeConverter(
+ 'http://test-uri/',
+ 'A',
+ new Encoder\MatchingValueEncoder(
+ encoderDetector: static fn (Encoder\Context $context, mixed $value): Encoder\Context => $context->withType(
+ property_exists($value, 'bar')
+ ? $context->type->copy('B')->withXmlTypeName('B')
+ : $context->type
+ ),
+ defaultEncoder: new Encoder\ObjectEncoder(stdClass::class)
+ )
+ );
+ }
+
+ protected function expectXml(): string
+ {
+ return <<
+
+
+
+
+ abc
+
+
+ def
+ ghi
+
+
+
+
+
+ XML;
+ }
+}
diff --git a/tests/PhpCompatibility/Implied/ImpliedSchema015Test.php b/tests/PhpCompatibility/Implied/ImpliedSchema015Test.php
new file mode 100644
index 0000000..e66ce27
--- /dev/null
+++ b/tests/PhpCompatibility/Implied/ImpliedSchema015Test.php
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOXML;
+ protected string $type = 'type="tns:return"';
+
+ protected function calculateParam(): mixed
+ {
+ return (object)[
+ 'responses' => [
+ new ImpliedSchema015A('abc'),
+ new ImpliedSchema015B('def', 'ghi'),
+ ],
+ ];
+ }
+
+ protected function registry(): EncoderRegistry
+ {
+ return parent::registry()
+ ->addClassMap('http://test-uri/', 'B', ImpliedSchema015B::class)
+ ->addComplexTypeConverter(
+ 'http://test-uri/',
+ 'A',
+ new Encoder\MatchingValueEncoder(
+ encoderDetector: static fn (Encoder\Context $context, mixed $value): array =>
+ $value instanceof ImpliedSchema015B
+ ? [
+ $context->withType($context->type->copy('B')->withXmlTypeName('B')),
+ new Encoder\ObjectEncoder(ImpliedSchema015B::class),
+ ]
+ : [$context],
+ defaultEncoder: new Encoder\ObjectEncoder(ImpliedSchema015A::class)
+ )
+ );
+ }
+
+ protected function expectXml(): string
+ {
+ return <<
+
+
+
+
+ abc
+
+
+ def
+ ghi
+
+
+
+
+
+ XML;
+ }
+}
+
+final class ImpliedSchema015A
+{
+ public function __construct(
+ public string $foo,
+ ) {
+ }
+}
+
+final class ImpliedSchema015B
+{
+ public function __construct(
+ public string $foo,
+ public string $bar,
+ ) {
+ }
+}