Skip to content

Commit 82844b9

Browse files
committed
Introduce message encoders for building soap servers
1 parent c3277f2 commit 82844b9

15 files changed

+1403
-46
lines changed
File renamed without changes.
File renamed without changes.

Diff for: examples/calc-http-server.php

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php declare(strict_types=1);
2+
3+
require_once \dirname(__DIR__) . '/vendor/autoload.php';
4+
5+
use Soap\Encoding\Encoder\Method\MethodContext;
6+
use Soap\Encoding\Encoder\Method\RequestEncoder;
7+
use Soap\Encoding\Encoder\Method\ResponseEncoder;
8+
use Soap\Encoding\EncoderRegistry;
9+
use Soap\Wsdl\Loader\StreamWrapperLoader;
10+
use Soap\WsdlReader\Locator\ServiceSelectionCriteria;
11+
use Soap\WsdlReader\Metadata\Wsdl1MetadataProvider;
12+
use Soap\WsdlReader\Wsdl1Reader;
13+
14+
$wsdlLocation = __DIR__ . '/calc.wsdl';
15+
$wsdl = (new Wsdl1Reader(new StreamWrapperLoader()))($wsdlLocation);
16+
$registry ??= EncoderRegistry::default()
17+
->addClassMap('http://tempuri.org/', 'Add', Add::class)
18+
->addClassMap('http://tempuri.org/', 'AddResponse', AddResponse::class);
19+
$metadataProvider = new Wsdl1MetadataProvider($wsdl, ServiceSelectionCriteria::defaults());
20+
$metadata = $metadataProvider->getMetadata();
21+
22+
// The soap action can be detected from a PSR-7 request headers by using:
23+
// https://github.com/php-soap/psr18-transport/blob/main/src/HttpBinding/SoapActionDetector.php
24+
$soapAction = 'Add';
25+
26+
$methodContext = new MethodContext(
27+
$metadata->getMethods()->fetchByName($soapAction),
28+
$metadata,
29+
$registry,
30+
$wsdl->namespaces,
31+
);
32+
33+
$request = <<<EOXML
34+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
35+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
36+
<soap:Body>
37+
<Add xmlns="http://tempuri.org/">
38+
<a>1</a>
39+
<b>2</b>
40+
</Add>
41+
</soap:Body>
42+
</soap:Envelope>
43+
EOXML;
44+
45+
$requestEncoder = new RequestEncoder();
46+
$requestIso = $requestEncoder->iso($methodContext);
47+
$arguments = $requestIso->from($request);
48+
49+
var_dump($arguments);
50+
51+
final class Add
52+
{
53+
public int $a;
54+
public int $b;
55+
}
56+
final class AddResponse
57+
{
58+
public function __construct(
59+
public int $AddResult,
60+
) {
61+
}
62+
}
63+
64+
$myCalculator = new class() {
65+
public function Add(Add $add): AddResponse
66+
{
67+
return new AddResponse($add->a + $add->b);
68+
}
69+
};
70+
71+
72+
$result = $myCalculator->{$soapAction}(...$arguments);
73+
74+
var_dump($result);
75+
76+
77+
$responseEncoder = new ResponseEncoder();
78+
$responseIso = $responseEncoder->iso($methodContext);
79+
$response = $responseIso->to([$result]);
80+
81+
var_dump($response);

Diff for: examples/calc-xsd-encoder.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types=1);
2+
3+
use GoetasWebservices\XML\XSDReader\SchemaReader;
4+
use Soap\Encoding\Encoder\Context;
5+
use Soap\Encoding\EncoderRegistry;
6+
use Soap\Engine\Metadata\Collection\MethodCollection;
7+
use Soap\Engine\Metadata\InMemoryMetadata;
8+
use Soap\WsdlReader\Metadata\Converter\SchemaToTypesConverter;
9+
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;
10+
use Soap\WsdlReader\Parser\Definitions\NamespacesParser;
11+
use VeeWee\Xml\Dom\Document;
12+
13+
require_once \dirname(__DIR__) . '/vendor/autoload.php';
14+
15+
// Load the XSD with the goetas-webservices/xsd-reader package and transform it to a metadata object:
16+
$xsd = Document::fromXmlFile($file = __DIR__.'/calc.xsd');
17+
$reader = new SchemaReader();
18+
$schema = $reader->readNode($xsd->locateDocumentElement(), $file);
19+
$namespaces = NamespacesParser::tryParse($xsd);
20+
$types = (new SchemaToTypesConverter())(
21+
$schema,
22+
TypesConverterContext::default($namespaces)
23+
);
24+
$metadata = new InMemoryMetadata($types, new MethodCollection());
25+
26+
// Create an encoder for the Add type context:
27+
$registry = EncoderRegistry::default();
28+
$encoder = $registry->detectEncoderForContext(
29+
$context = new Context(
30+
$types->fetchFirstByName('Add')->getXsdType(),
31+
$metadata,
32+
$registry,
33+
$namespaces,
34+
)
35+
);
36+
37+
// Decode + Encode the Add type:
38+
var_dump($data = $encoder->iso($context)->from(
39+
<<<EOXML
40+
<Add xmlns="http://tempuri.org/">
41+
<a>1</a>
42+
<b>2</b>
43+
</Add>
44+
EOXML
45+
));
46+
47+
var_dump($encoder->iso($context)->to($data));

Diff for: examples/calc.xsd

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<s:schema elementFormDefault="qualified" xmlns:tns="http://tempuri.org/" targetNamespace="http://tempuri.org/"
2+
xmlns:s="http://www.w3.org/2001/XMLSchema">
3+
<s:element name="Add">
4+
<s:complexType>
5+
<s:sequence>
6+
<s:element minOccurs="1" maxOccurs="1" name="a" type="s:int"/>
7+
<s:element minOccurs="1" maxOccurs="1" name="b" type="s:int"/>
8+
</s:sequence>
9+
</s:complexType>
10+
</s:element>
11+
<s:element name="AddResponse">
12+
<s:complexType>
13+
<s:sequence>
14+
<s:element minOccurs="1" maxOccurs="1" name="AddResult" type="s:int"/>
15+
</s:sequence>
16+
</s:complexType>
17+
</s:element>
18+
<s:element name="Subtract">
19+
<s:complexType>
20+
<s:sequence>
21+
<s:element minOccurs="1" maxOccurs="1" name="a" type="s:int"/>
22+
<s:element minOccurs="1" maxOccurs="1" name="b" type="s:int"/>
23+
</s:sequence>
24+
</s:complexType>
25+
</s:element>
26+
<s:element name="SubtractResponse">
27+
<s:complexType>
28+
<s:sequence>
29+
<s:element minOccurs="1" maxOccurs="1" name="SubtractResult" type="s:int"/>
30+
</s:sequence>
31+
</s:complexType>
32+
</s:element>
33+
</s:schema>

Diff for: src/Decoder.php

+8-21
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@
33

44
namespace Soap\Encoding;
55

6-
use Soap\Encoding\Encoder\Context;
7-
use Soap\Encoding\Xml\Reader\OperationReader;
6+
use Soap\Encoding\Encoder\Method\MethodContext;
7+
use Soap\Encoding\Encoder\Method\ResponseEncoder;
88
use Soap\Engine\Decoder as SoapDecoder;
99
use Soap\Engine\HttpBinding\SoapResponse;
1010
use Soap\Engine\Metadata\Metadata;
11-
use Soap\WsdlReader\Model\Definitions\BindingUse;
1211
use Soap\WsdlReader\Model\Definitions\Namespaces;
1312
use function count;
14-
use function Psl\invariant;
15-
use function Psl\Vec\map;
1613

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

35-
$returnType = $methodInfo->getReturnType();
36-
$context = new Context($returnType, $this->metadata, $this->registry, $this->namespaces, $bindingUse);
37-
$decoder = $this->registry->detectEncoderForContext($context);
38-
$iso = $decoder->iso($context);
39-
40-
// The SoapResponse only contains the payload of the response (with no headers).
41-
// It can be parsed directly as XML.
4232
$payload = $response->getPayload();
43-
invariant($payload !== '', 'Expected a non-empty response payload. Received an empty HTTP response');
44-
$parts = (new OperationReader($meta))($payload)->elements();
33+
/** @var list<mixed> $parts */
34+
$parts = $iso->from($payload);
4535

4636
return match(count($parts)) {
4737
0 => null,
48-
1 => $iso->from($parts[0]),
49-
default => map(
50-
$parts,
51-
static fn (string $part): mixed => $iso->from($part)
52-
),
38+
1 => $parts[0],
39+
default => $parts,
5340
};
5441
}
5542
}

Diff for: src/Encoder.php

+6-24
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@
33

44
namespace Soap\Encoding;
55

6-
use Soap\Encoding\Encoder\Context;
7-
use Soap\Encoding\Xml\Writer\OperationBuilder;
8-
use Soap\Encoding\Xml\Writer\ParameterBuilder;
9-
use Soap\Encoding\Xml\Writer\SoapEnvelopeWriter;
6+
use Soap\Encoding\Encoder\Method\MethodContext;
7+
use Soap\Encoding\Encoder\Method\RequestEncoder;
108
use Soap\Engine\Encoder as SoapEncoder;
119
use Soap\Engine\HttpBinding\SoapRequest;
1210
use Soap\Engine\Metadata\Metadata;
13-
use Soap\WsdlReader\Model\Definitions\BindingUse;
14-
use Soap\WsdlReader\Model\Definitions\EncodingStyle;
1511
use Soap\WsdlReader\Model\Definitions\Namespaces;
1612
use Soap\WsdlReader\Model\Definitions\SoapVersion;
17-
use function VeeWee\Reflecta\Lens\index;
13+
use function Psl\Type\mixed_vec;
1814

1915
final class Encoder implements SoapEncoder
2016
{
@@ -29,26 +25,12 @@ public function encode(string $method, array $arguments): SoapRequest
2925
{
3026
$methodInfo = $this->metadata->getMethods()->fetchByName($method);
3127
$meta = $methodInfo->getMeta();
32-
28+
$methodContext = new MethodContext($methodInfo, $this->metadata, $this->registry, $this->namespaces);
3329
$soapVersion = $meta->soapVersion()->map(SoapVersion::from(...))->unwrapOr(SoapVersion::SOAP_12);
34-
$bindingUse = $meta->inputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL);
35-
$encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::from(...));
36-
37-
$requestParams = [];
38-
foreach ($methodInfo->getParameters() as $index => $parameter) {
39-
$type = $parameter->getType();
40-
$context = new Context($type, $this->metadata, $this->registry, $this->namespaces, $bindingUse);
41-
/** @var mixed $value */
42-
$value = index($index)->get($arguments);
43-
44-
$requestParams[] = (new ParameterBuilder($meta, $context, $value))(...);
45-
}
46-
47-
$operation = new OperationBuilder($meta, $this->namespaces, $requestParams);
48-
$writeEnvelope = new SoapEnvelopeWriter($soapVersion, $bindingUse, $encodingStyle, $operation(...));
30+
$iso = (new RequestEncoder())->iso($methodContext);
4931

5032
return new SoapRequest(
51-
$writeEnvelope() . PHP_EOL,
33+
$iso->to(mixed_vec()->assert($arguments)),
5234
$meta->location()->unwrap(),
5335
$meta->action()->unwrap(),
5436
match($soapVersion) {

Diff for: src/Encoder/Method/MethodContext.php

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Soap\Encoding\Encoder\Method;
4+
5+
use Soap\Encoding\Encoder\Context;
6+
use Soap\Encoding\EncoderRegistry;
7+
use Soap\Engine\Metadata\Metadata;
8+
use Soap\Engine\Metadata\Model\Method;
9+
use Soap\Engine\Metadata\Model\XsdType;
10+
use Soap\WsdlReader\Model\Definitions\BindingUse;
11+
use Soap\WsdlReader\Model\Definitions\Namespaces;
12+
13+
final class MethodContext
14+
{
15+
public function __construct(
16+
public readonly Method $method,
17+
public readonly Metadata $metadata,
18+
public readonly EncoderRegistry $registry,
19+
public readonly Namespaces $namespaces,
20+
public readonly BindingUse $bindingUse = BindingUse::LITERAL,
21+
) {
22+
}
23+
24+
public function createXmlEncoderContextForType(XsdType $type): Context
25+
{
26+
return new Context(
27+
$type,
28+
$this->metadata,
29+
$this->registry,
30+
$this->namespaces,
31+
$this->bindingUse
32+
);
33+
}
34+
35+
public function withBindingUse(BindingUse $bindingUse): self
36+
{
37+
return new self(
38+
$this->method,
39+
$this->metadata,
40+
$this->registry,
41+
$this->namespaces,
42+
$bindingUse
43+
);
44+
}
45+
}

0 commit comments

Comments
 (0)