Skip to content

Commit

Permalink
Merge pull request #27 from veewee/element-aware-encoders
Browse files Browse the repository at this point in the history
Introduce element aware encoders
  • Loading branch information
veewee authored Sep 30, 2024
2 parents a8f06bf + ba88883 commit 3deec36
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 9 deletions.
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'],
];
}
}

0 comments on commit 3deec36

Please sign in to comment.