Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-style.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-suggest ${{ matrix.composer-options }}
- name: Run the tests
run: PHP_CS_FIXER_IGNORE_ENV=1 ./tools/php-cs-fixer.phar fix --dry-run
run: PHP_CS_FIXER_IGNORE_ENV=1 ./tools/php-cs-fixer.phar fix --dry-run --diff
49 changes: 49 additions & 0 deletions examples/encoders/simpleType/anyXml.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types=1);
require_once \dirname(__DIR__, 3) . '/vendor/autoload.php';

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\Feature\ElementAware;
use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\EncoderRegistry;
use VeeWee\Reflecta\Iso\Iso;
use function VeeWee\Xml\Encoding\document_encode;
use function VeeWee\Xml\Encoding\xml_decode;

/**
* Most of the time, you don't need access to the wrapping XML element from within a simple type encoder.
* Sometimes, when using anyXml or anyType, you might want to have full control over the wrapping element.
* This allows you to use 3rd party tools to build the full XML structure from within a simple type encoder.
*
* Do note that:
* - You'll need to check if the current provided type is an attribute or not.
* - If you want to add xsi:type information, you need to add / parse it manually.
* - The result will be used as a raw XML input, meaning it should be valid XML (without the header declearations).
*/
EncoderRegistry::default()
->addSimpleTypeConverter(
'http://www.w3.org/2001/XMLSchema',
'anyXml',
new class implements
ElementAware,
XmlEncoder {
public function iso(Context $context): Iso
{
if ($context->type->getMeta()->isAttribute()->unwrapOr(false)) {
return (new ScalarTypeEncoder())->iso($context);
}

$targetElementName = $context->type->getXmlTargetNodeName();
return new Iso(
to: static fn (array $data): string => document_encode([$targetElementName => $data])
->manipulate(static fn (\DOMDocument $document) => $document->documentElement->setAttributeNS(
VeeWee\Xml\Xmlns\Xmlns::xsi()->value(),
'xsi:type',
'custom:type'
))
->stringifyDocumentElement(),
from: static fn (string $xml): array => xml_decode($xml)[$targetElementName],
);
}
}
);
2 changes: 1 addition & 1 deletion src/Encoder/ElementEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/**
* @implements XmlEncoder<mixed, string>
*/
final class ElementEncoder implements XmlEncoder
final class ElementEncoder implements Feature\ElementAware, XmlEncoder
{
/**
* @param XmlEncoder<mixed, string> $typeEncoder
Expand Down
11 changes: 11 additions & 0 deletions src/Encoder/Feature/ElementAware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace Soap\Encoding\Encoder\Feature;

/**
* Tells the encoder knows how to encode elements.
* It can be used on simpleType encoders so that you get in control about the wrapping XML element.
*/
interface ElementAware
{
}
2 changes: 1 addition & 1 deletion src/Encoder/ObjectEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*
* @implements XmlEncoder<TObj|array, non-empty-string>
*/
final class ObjectEncoder implements XmlEncoder
final class ObjectEncoder implements Feature\ElementAware, XmlEncoder
{
/**
* @param class-string<TObj> $className
Expand Down
2 changes: 1 addition & 1 deletion src/Encoder/OptionalElementEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @template T of mixed
* @implements XmlEncoder<T, string>
*/
final class OptionalElementEncoder implements Feature\OptionalAware, XmlEncoder
final class OptionalElementEncoder implements Feature\ElementAware, Feature\OptionalAware, XmlEncoder
{
/**
* @param XmlEncoder<T, string> $elementEncoder
Expand Down
2 changes: 1 addition & 1 deletion src/Encoder/RepeatingElementEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @template T
* @implements XmlEncoder<iterable<array-key, T>|null, string>
*/
final class RepeatingElementEncoder implements Feature\ListAware, XmlEncoder
final class RepeatingElementEncoder implements Feature\ElementAware, Feature\ListAware, XmlEncoder
{
/**
* @param XmlEncoder<T, string> $typeEncoder
Expand Down
6 changes: 4 additions & 2 deletions src/Encoder/SimpleType/EncoderDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ public function __invoke(Context $context): XmlEncoder
}

if ($meta->isElement()->unwrapOr(false)) {
$encoder = new ElementEncoder($encoder);
if (!$encoder instanceof Feature\ElementAware) {
$encoder = new ElementEncoder($encoder);
}

if ($meta->isNullable()->unwrapOr(false)) {
if ($meta->isNullable()->unwrapOr(false) && !$encoder instanceof Feature\OptionalAware) {
$encoder = new OptionalElementEncoder($encoder);
}
}
Expand Down
5 changes: 3 additions & 2 deletions tests/Unit/ContextCreatorTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ trait ContextCreatorTrait
{
public static function createContext(
XsdType $currentType,
TypeCollection $allTypes = new TypeCollection()
TypeCollection $allTypes = new TypeCollection(),
?EncoderRegistry $encoderRegistry = null
): Context {
return new Context(
$currentType,
new InMemoryMetadata(
$allTypes,
new MethodCollection(),
),
EncoderRegistry::default(),
$encoderRegistry ?? EncoderRegistry::default(),
self::buildNamespaces(),
);
}
Expand Down
54 changes: 54 additions & 0 deletions tests/Unit/Encoder/Feature/ElementAwareEncoderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types=1);

namespace Soap\Encoding\Test\Unit\Encoder\Feature;

use PHPUnit\Framework\Attributes\CoversClass;
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\Feature\ElementAware;
use Soap\Encoding\Encoder\SimpleType\EncoderDetector;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\EncoderRegistry;
use Soap\Encoding\Test\Unit\Encoder\AbstractEncoderTests;
use Soap\Engine\Metadata\Model\XsdType;
use Soap\Xml\Xmlns;
use VeeWee\Reflecta\Iso\Iso;
use function VeeWee\Xml\Encoding\document_encode;
use function VeeWee\Xml\Encoding\xml_decode;

#[CoversClass(EncoderDetector::class)]
final class ElementAwareEncoderTest extends AbstractEncoderTests
{
public static function provideIsomorphicCases(): iterable
{
$registry = EncoderRegistry::default()
->addSimpleTypeConverter(
Xmlns::xsd()->value(),
'anyType',
new class implements
ElementAware,
XmlEncoder {
public function iso(Context $context): Iso
{
$typeName = $context->type->getXmlTargetNodeName();
return new Iso(
to: static fn (array $data): string => document_encode([$typeName => $data])->stringifyDocumentElement(),
from: static fn (string $xml): array => xml_decode($xml)[$typeName],
);
}
}
);

$context = self::createContext(
XsdType::any()->withXmlTargetNodeName('data'),
encoderRegistry: $registry
);
$encoder = $registry->detectEncoderForContext($context);

yield 'element-aware-simple-type' => [
'encoder' => $encoder,
'context' => $context,
'xml' => '<data><key>value</key></data>',
'data' => ['key' => 'value'],
];
}
}