Skip to content

Commit

Permalink
Introduce message encoders for building soap servers
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Jan 3, 2025
1 parent c3277f2 commit 6c59395
Show file tree
Hide file tree
Showing 15 changed files with 1,400 additions and 46 deletions.
File renamed without changes.
File renamed without changes.
78 changes: 78 additions & 0 deletions examples/calc-http-server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php declare(strict_types=1);

require_once \dirname(__DIR__) . '/vendor/autoload.php';

use Soap\Encoding\Encoder\Method\MethodContext;
use Soap\Encoding\Encoder\Method\RequestEncoder;
use Soap\Encoding\Encoder\Method\ResponseEncoder;
use Soap\Encoding\EncoderRegistry;
use Soap\Wsdl\Loader\StreamWrapperLoader;
use Soap\WsdlReader\Locator\ServiceSelectionCriteria;
use Soap\WsdlReader\Metadata\Wsdl1MetadataProvider;
use Soap\WsdlReader\Wsdl1Reader;

$wsdlLocation = __DIR__ . '/calc.wsdl';
$wsdl = (new Wsdl1Reader(new StreamWrapperLoader()))($wsdlLocation);
$registry ??= EncoderRegistry::default()
->addClassMap('http://tempuri.org/', 'Add', Add::class)
->addClassMap('http://tempuri.org/', 'AddResponse', AddResponse::class);
$metadataProvider = new Wsdl1MetadataProvider($wsdl, ServiceSelectionCriteria::defaults());
$metadata = $metadataProvider->getMetadata();

$soapAction = 'Add';
$methodContext = new MethodContext(
$metadata->getMethods()->fetchByName($soapAction),
$metadata,
$registry,
$wsdl->namespaces,
);

$request = <<<EOXML
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<Add xmlns="http://tempuri.org/">
<a>1</a>
<b>2</b>
</Add>
</soap:Body>
</soap:Envelope>
EOXML;

$requestEncoder = new RequestEncoder();
$requestIso = $requestEncoder->iso($methodContext);
$arguments = $requestIso->from($request);

var_dump($arguments);

final class Add
{
public int $a;
public int $b;
}
final class AddResponse
{
public function __construct(
public int $AddResult,
) {
}
}

$myCalculator = new class() {
public function Add(Add $add): AddResponse
{
return new AddResponse($add->a + $add->b);
}
};


$result = $myCalculator->{$soapAction}(...$arguments);

var_dump($result);


$responseEncoder = new ResponseEncoder();
$responseIso = $responseEncoder->iso($methodContext);
$response = $responseIso->to([$result]);

var_dump($response);
47 changes: 47 additions & 0 deletions examples/calc-xsd-encoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);

use GoetasWebservices\XML\XSDReader\SchemaReader;
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\EncoderRegistry;
use Soap\Engine\Metadata\Collection\MethodCollection;
use Soap\Engine\Metadata\InMemoryMetadata;
use Soap\WsdlReader\Metadata\Converter\SchemaToTypesConverter;
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;
use Soap\WsdlReader\Parser\Definitions\NamespacesParser;
use VeeWee\Xml\Dom\Document;

require_once \dirname(__DIR__) . '/vendor/autoload.php';

// Load the XSD with the goetas-webservices/xsd-reader package and transform it to a metadata object:
$xsd = Document::fromXmlFile($file = __DIR__.'/calc.xsd');
$reader = new SchemaReader();
$schema = $reader->readNode($xsd->locateDocumentElement(), $file);
$namespaces = NamespacesParser::tryParse($xsd);
$types = (new SchemaToTypesConverter())(
$schema,
TypesConverterContext::default($namespaces)
);
$metadata = new InMemoryMetadata($types, new MethodCollection());

// Create an encoder for the Add type context:
$registry = EncoderRegistry::default();
$encoder = $registry->detectEncoderForContext(
$context = new Context(
$types->fetchFirstByName('Add')->getXsdType(),
$metadata,
$registry,
$namespaces,
)
);

// Decode + Encode the Add type:
var_dump($data = $encoder->iso($context)->from(
<<<EOXML
<Add xmlns="http://tempuri.org/">
<a>1</a>
<b>2</b>
</Add>
EOXML
));

var_dump($encoder->iso($context)->to($data));
33 changes: 33 additions & 0 deletions examples/calc.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<s:schema elementFormDefault="qualified" xmlns:tns="http://tempuri.org/" targetNamespace="http://tempuri.org/"
xmlns:s="http://www.w3.org/2001/XMLSchema">
<s:element name="Add">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="a" type="s:int"/>
<s:element minOccurs="1" maxOccurs="1" name="b" type="s:int"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="AddResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="AddResult" type="s:int"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="Subtract">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="a" type="s:int"/>
<s:element minOccurs="1" maxOccurs="1" name="b" type="s:int"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="SubtractResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="SubtractResult" type="s:int"/>
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
29 changes: 8 additions & 21 deletions src/Decoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@

namespace Soap\Encoding;

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Xml\Reader\OperationReader;
use Soap\Encoding\Encoder\Method\MethodContext;
use Soap\Encoding\Encoder\Method\ResponseEncoder;
use Soap\Engine\Decoder as SoapDecoder;
use Soap\Engine\HttpBinding\SoapResponse;
use Soap\Engine\Metadata\Metadata;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use Soap\WsdlReader\Model\Definitions\Namespaces;
use function count;
use function Psl\invariant;
use function Psl\Vec\map;

final class Decoder implements SoapDecoder
{
Expand All @@ -29,27 +26,17 @@ public function __construct(
public function decode(string $method, SoapResponse $response): mixed
{
$methodInfo = $this->metadata->getMethods()->fetchByName($method);
$meta = $methodInfo->getMeta();
$bindingUse = $meta->outputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL);
$methodContext = new MethodContext($methodInfo, $this->metadata, $this->registry, $this->namespaces);
$iso = (new ResponseEncoder())->iso($methodContext);

$returnType = $methodInfo->getReturnType();
$context = new Context($returnType, $this->metadata, $this->registry, $this->namespaces, $bindingUse);
$decoder = $this->registry->detectEncoderForContext($context);
$iso = $decoder->iso($context);

// The SoapResponse only contains the payload of the response (with no headers).
// It can be parsed directly as XML.
$payload = $response->getPayload();
invariant($payload !== '', 'Expected a non-empty response payload. Received an empty HTTP response');
$parts = (new OperationReader($meta))($payload)->elements();
/** @var list<mixed> $parts */
$parts = $iso->from($payload);

return match(count($parts)) {
0 => null,
1 => $iso->from($parts[0]),
default => map(
$parts,
static fn (string $part): mixed => $iso->from($part)
),
1 => $parts[0],
default => $parts,
};
}
}
30 changes: 6 additions & 24 deletions src/Encoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@

namespace Soap\Encoding;

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Xml\Writer\OperationBuilder;
use Soap\Encoding\Xml\Writer\ParameterBuilder;
use Soap\Encoding\Xml\Writer\SoapEnvelopeWriter;
use Soap\Encoding\Encoder\Method\MethodContext;
use Soap\Encoding\Encoder\Method\RequestEncoder;
use Soap\Engine\Encoder as SoapEncoder;
use Soap\Engine\HttpBinding\SoapRequest;
use Soap\Engine\Metadata\Metadata;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use Soap\WsdlReader\Model\Definitions\EncodingStyle;
use Soap\WsdlReader\Model\Definitions\Namespaces;
use Soap\WsdlReader\Model\Definitions\SoapVersion;
use function VeeWee\Reflecta\Lens\index;
use function Psl\Type\mixed_vec;

final class Encoder implements SoapEncoder
{
Expand All @@ -29,26 +25,12 @@ public function encode(string $method, array $arguments): SoapRequest
{
$methodInfo = $this->metadata->getMethods()->fetchByName($method);
$meta = $methodInfo->getMeta();

$methodContext = new MethodContext($methodInfo, $this->metadata, $this->registry, $this->namespaces);
$soapVersion = $meta->soapVersion()->map(SoapVersion::from(...))->unwrapOr(SoapVersion::SOAP_12);
$bindingUse = $meta->inputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL);
$encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::from(...));

$requestParams = [];
foreach ($methodInfo->getParameters() as $index => $parameter) {
$type = $parameter->getType();
$context = new Context($type, $this->metadata, $this->registry, $this->namespaces, $bindingUse);
/** @var mixed $value */
$value = index($index)->get($arguments);

$requestParams[] = (new ParameterBuilder($meta, $context, $value))(...);
}

$operation = new OperationBuilder($meta, $this->namespaces, $requestParams);
$writeEnvelope = new SoapEnvelopeWriter($soapVersion, $bindingUse, $encodingStyle, $operation(...));
$iso = (new RequestEncoder())->iso($methodContext);

return new SoapRequest(
$writeEnvelope() . PHP_EOL,
$iso->to(mixed_vec()->assert($arguments)),
$meta->location()->unwrap(),
$meta->action()->unwrap(),
match($soapVersion) {
Expand Down
45 changes: 45 additions & 0 deletions src/Encoder/Method/MethodContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types=1);

namespace Soap\Encoding\Encoder\Method;

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\EncoderRegistry;
use Soap\Engine\Metadata\Metadata;
use Soap\Engine\Metadata\Model\Method;
use Soap\Engine\Metadata\Model\XsdType;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use Soap\WsdlReader\Model\Definitions\Namespaces;

final class MethodContext
{
public function __construct(
public readonly Method $method,
public readonly Metadata $metadata,
public readonly EncoderRegistry $registry,
public readonly Namespaces $namespaces,
public readonly BindingUse $bindingUse = BindingUse::LITERAL,
) {
}

public function createXmlEncoderContextForType(XsdType $type): Context
{
return new Context(
$type,
$this->metadata,
$this->registry,
$this->namespaces,
$this->bindingUse
);
}

public function withBindingUse(BindingUse $bindingUse): self
{
return new self(
$this->method,
$this->metadata,
$this->registry,
$this->namespaces,
$bindingUse
);
}
}
Loading

0 comments on commit 6c59395

Please sign in to comment.