Skip to content

Commit

Permalink
Merge pull request #22 from veewee/alternate-resolveXsiType
Browse files Browse the repository at this point in the history
Introduce a XsiTypeCalculator feature that can enhance xsi:type calculation from the encoder
  • Loading branch information
veewee authored Aug 30, 2024
2 parents abbf352 + a7aa57d commit 0d1de01
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 8 deletions.
31 changes: 30 additions & 1 deletion examples/encoders/simpleType/anyType-with-xsi-info.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\Feature\ElementContextEnhancer;
use Soap\Encoding\Encoder\Feature\XsiTypeCalculator;
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\EncoderRegistry;
use Soap\Encoding\Xml\Writer\ElementValueBuilder;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use VeeWee\Reflecta\Iso\Iso;

Expand All @@ -31,7 +33,8 @@
'anyType',
new class implements
ElementContextEnhancer,
XmlEncoder {
XmlEncoder,
XsiTypeCalculator {
public function iso(Context $context): Iso
{
return (new ScalarTypeEncoder())->iso($context);
Expand All @@ -45,5 +48,31 @@ public function enhanceElementContext(Context $context): Context
{
return $context->withBindingUse(BindingUse::ENCODED);
}

/**
* Can be used to fine-tune the xsi:type element.
* For example, xsi:type="xsd:date" when dealing with value's like `DateTimeImmutable`.
*
* A default fallback function is provided in the ElementValueBuilder class.
*/
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),
};
}

/**
* Determines if the xmlns of the xsi:type prefix should be imported.
* For example: xsd:date will import xmlns:xsd="...".
*
* A default fallback function is provided in the ElementValueBuilder class.
*/
public function shouldIncludeXsiTargetNamespace(Context $context): bool
{
return ElementValueBuilder::shouldIncludeXsiTargetNamespace($context);
}
}
);
4 changes: 4 additions & 0 deletions src/Encoder/Feature/DisregardXsiInformation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

namespace Soap\Encoding\Encoder\Feature;

/**
* Tells the decoder to disregard any xsi:type information on the element when decoding an element.
* It will use the original provided decoder by default and won't try to guess the decoder based on xsi:type.
*/
interface DisregardXsiInformation
{
}
27 changes: 27 additions & 0 deletions src/Encoder/Feature/XsiTypeCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

namespace Soap\Encoding\Encoder\Feature;

use Soap\Encoding\Encoder\Context;

/**
* 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.
*/
interface XsiTypeCalculator
{
/**
* @return string The value for the xsi:type attribute
*
* A sensible default fallback function is provided in the `ElementValueBuilder` class.
*/
public function resolveXsiTypeForValue(Context $context, mixed $value): string;


/**
* Tells the XsiAttributeBuilder that the prefix of the xsi:type should be imported as a xmlns namespace.
*
* A sensible default fallback function is provided in the `ElementValueBuilder` class.
*/
public function shouldIncludeXsiTargetNamespace(Context $context): bool;
}
41 changes: 35 additions & 6 deletions src/Xml/Writer/ElementValueBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use Generator;
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\Feature\CData;
use Soap\Encoding\Encoder\Feature;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\TypeInference\XsiTypeDetector;
use Soap\WsdlReader\Model\Definitions\BindingUse;
Expand Down Expand Up @@ -48,16 +48,45 @@ private function buildXsiType(XMLWriter $writer): Generator
}

$context = $this->context;
$type = $context->type;
[$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(
$this->context,
XsiTypeDetector::detectFromValue($context, $this->value),
includeXsiTargetNamespace: $type->getXmlTargetNamespace() !== $type->getXmlNamespace()
|| !$type->getMeta()->isQualified()->unwrapOr(false)
$xsiType,
$includeXsiTargetNamespace,
))($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.
*/
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<bool>
*/
Expand All @@ -66,7 +95,7 @@ private function buildValue(XMLWriter $writer): Generator
$encoded = $this->encoder->iso($this->context)->to($this->value);

$builder = match (true) {
$this->encoder instanceof CData => cdata(value($encoded)),
$this->encoder instanceof Feature\CData => cdata(value($encoded)),
default => value($encoded)
};

Expand Down
34 changes: 33 additions & 1 deletion tests/Unit/Encoder/ElementEncoderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\ElementEncoder;
use Soap\Encoding\Encoder\Feature\ElementContextEnhancer;
use Soap\Encoding\Encoder\Feature\XsiTypeCalculator;
use Soap\Encoding\Encoder\SimpleType\IntTypeEncoder;
use Soap\Encoding\Encoder\SimpleType\StringTypeEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\Xml\Node\Element;
use Soap\Engine\Metadata\Model\TypeMeta;
use Soap\Engine\Metadata\Model\XsdType;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use VeeWee\Reflecta\Iso\Iso;

#[CoversClass(ElementEncoder::class)]
Expand All @@ -25,7 +27,11 @@ public static function provideIsomorphicCases(): iterable
'context' => $context = self::createContext(
$xsdType = XsdType::guess('string')
->withXmlTargetNodeName('hello')
->withMeta(static fn (TypeMeta $meta): TypeMeta => $meta->withIsQualified(true))
->withMeta(
static fn (TypeMeta $meta): TypeMeta => $meta
->withIsQualified(true)
->withIsSimple(true)
)
),
];

Expand Down Expand Up @@ -79,6 +85,32 @@ public function enhanceElementContext(Context $context): Context
'xml' => '<bonjour>32</bonjour>',
'data' => 32,
];
yield 'xsi-type-calculating-encoder' => [
...$baseConfig,
'encoder' => $encoder = new ElementEncoder(new class implements ElementContextEnhancer, XmlEncoder, XsiTypeCalculator {
public function iso(Context $context): Iso
{
return (new IntTypeEncoder())->iso($context);
}

public function enhanceElementContext(Context $context): Context
{
return $context->withBindingUse(BindingUse::ENCODED);
}

public function resolveXsiTypeForValue(Context $context, mixed $value): string
{
return 'xsd:'.get_debug_type($value);
}

public function shouldIncludeXsiTargetNamespace(Context $context): bool
{
return true;
}
}),
'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>',
'data' => 32,
];
}

public function test_it_can_decode_from_xml_item(): void
Expand Down

0 comments on commit 0d1de01

Please sign in to comment.