diff --git a/.github/workflows/analyzers.yaml b/.github/workflows/analyzers.yaml
new file mode 100644
index 0000000..b67d45e
--- /dev/null
+++ b/.github/workflows/analyzers.yaml
@@ -0,0 +1,25 @@
+name: Analyzers
+
+on: [push, pull_request]
+jobs:
+ run:
+ runs-on: ${{ matrix.operating-system }}
+ strategy:
+ matrix:
+ operating-system: [ubuntu-latest]
+ php-versions: ['8.1', '8.2', '8.3']
+ fail-fast: false
+ name: PHP ${{ matrix.php-versions }} @ ${{ matrix.operating-system }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@master
+ - name: Install PHP
+ uses: shivammathur/setup-php@master
+ with:
+ php-version: ${{ matrix.php-versions }}
+ tools: 'composer:v2'
+ extensions: pcov, mbstring, posix, dom, soap
+ - name: Install dependencies
+ run: composer update --prefer-dist --no-progress --no-suggest ${{ matrix.composer-options }}
+ - name: Run the tests
+ run: ./vendor/bin/psalm
diff --git a/composer.json b/composer.json
index 8ae232f..d1744fe 100644
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
"require": {
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"azjezz/psl": "^2.9.0",
- "veewee/reflecta": "^0.4.0",
+ "veewee/reflecta": "^0.5.0",
"veewee/xml": "^3.1",
"php-soap/engine": "^2.9.0",
"php-soap/wsdl": "^1.6",
diff --git a/psalm.xml b/psalm.xml
index 16d1a2c..ec15026 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -14,4 +14,15 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Decoder.php b/src/Decoder.php
index 39ab3fd..1275179 100644
--- a/src/Decoder.php
+++ b/src/Decoder.php
@@ -10,6 +10,7 @@
use Soap\Engine\Metadata\Metadata;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use Soap\WsdlReader\Model\Definitions\Namespaces;
+use function Psl\Type\non_empty_string;
use function Psl\Vec\map;
final class Decoder implements SoapDecoder
@@ -21,6 +22,9 @@ public function __construct(
) {
}
+ /**
+ * @psalm-return mixed
+ */
public function decode(string $method, SoapResponse $response): mixed
{
$methodInfo = $this->metadata->getMethods()->fetchByName($method);
@@ -30,15 +34,21 @@ public function decode(string $method, SoapResponse $response): mixed
$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.
- $parts = (new OperationReader($meta))($response->getPayload());
+ $parts = (new OperationReader($meta))(
+ non_empty_string()->assert($response->getPayload())
+ );
return match(count($parts)) {
0 => null,
- 1 => $decoder->iso($context)->from($parts[0]),
- default => map($parts, $decoder->iso($context)->from(...)),
+ 1 => $iso->from($parts[0]),
+ default => map(
+ $parts,
+ static fn (string $part): mixed => $iso->from($part)
+ ),
};
}
}
diff --git a/src/Driver.php b/src/Driver.php
index f9eaf29..1ff090f 100644
--- a/src/Driver.php
+++ b/src/Driver.php
@@ -25,7 +25,7 @@ public static function createFromWsdl1(
Wsdl1 $wsdl,
?ServiceSelectionCriteria $serviceSelectionCriteria = null,
?EncoderRegistry $registry = null
- ) {
+ ): self {
$registry ??= EncoderRegistry::default();
$metadataProvider = new Wsdl1MetadataProvider(
$wsdl,
@@ -54,7 +54,9 @@ public static function createFromMetadata(
);
}
-
+ /**
+ * @return mixed
+ */
public function decode(string $method, SoapResponse $response)
{
return $this->decoder->decode($method, $response);
diff --git a/src/Encoder.php b/src/Encoder.php
index 1edc22d..96a4820 100644
--- a/src/Encoder.php
+++ b/src/Encoder.php
@@ -13,6 +13,8 @@
use Soap\WsdlReader\Model\Definitions\EncodingStyle;
use Soap\WsdlReader\Model\Definitions\Namespaces;
use Soap\WsdlReader\Model\Definitions\SoapVersion;
+use function Psl\Type\mixed;
+use function Psl\Type\non_empty_string;
use function VeeWee\Reflecta\Lens\index;
final class Encoder implements SoapEncoder
@@ -31,15 +33,18 @@ public function encode(string $method, array $arguments): SoapRequest
$soapVersion = $meta->soapVersion()->map(SoapVersion::from(...))->unwrapOr(SoapVersion::SOAP_12);
$bindingUse = $meta->inputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL);
- $encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::tryFrom(...));
+ $encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::from(...));
$request = [];
foreach ($methodInfo->getParameters() as $index => $parameter) {
$type = $parameter->getType();
$context = new Context($type, $this->metadata, $this->registry, $this->namespaces, $bindingUse);
+ /** @var mixed $argument */
$argument = index($index)->get($arguments);
- $request[] = $this->registry->detectEncoderForContext($context)->iso($context)->to($argument);
+ $request[] = non_empty_string()->assert(
+ $this->registry->detectEncoderForContext($context)->iso($context)->to($argument)
+ );
}
$operation = new OperationBuilder($meta, $this->namespaces, $request);
diff --git a/src/Encoder/Context.php b/src/Encoder/Context.php
index c37b987..e512347 100644
--- a/src/Encoder/Context.php
+++ b/src/Encoder/Context.php
@@ -11,6 +11,9 @@
final class Context
{
+ /**
+ * TODO : READONLY !
+ */
public function __construct(
public /*readonly*/ XsdType $type,
public /*readonly*/ Metadata $metadata,
diff --git a/src/Encoder/ElementEncoder.php b/src/Encoder/ElementEncoder.php
index 9e7300a..28d0100 100644
--- a/src/Encoder/ElementEncoder.php
+++ b/src/Encoder/ElementEncoder.php
@@ -10,13 +10,12 @@
use VeeWee\Xml\Dom\Document;
/**
- * @template T of mixed
- * @implements XmlEncoder
+ * @implements XmlEncoder
*/
final class ElementEncoder implements XmlEncoder
{
/**
- * @param XmlEncoder $typeEncoder
+ * @param XmlEncoder $typeEncoder
*/
public function __construct(
private readonly XmlEncoder $typeEncoder
@@ -24,7 +23,7 @@ public function __construct(
}
/**
- * @return Iso
+ * @return Iso
*/
public function iso(Context $context): Iso
{
@@ -32,10 +31,17 @@ public function iso(Context $context): Iso
$typeIso = $typeEncoder->iso($context);
return new Iso(
+ /**
+ * @psalm-param mixed $raw
+ */
static fn (mixed $raw): string => (new XsdTypeXmlElementWriter())(
$context,
(new ElementValueBuilder($context, $typeIso, $raw))
),
+ /**
+ * @psalm-param non-empty-string $xml
+ * @psalm-return mixed
+ */
static fn (string $xml): mixed => (new ElementValueReader())(
$context,
$typeEncoder,
diff --git a/src/Encoder/EncoderDetector.php b/src/Encoder/EncoderDetector.php
index 4fe2c8b..64e4408 100644
--- a/src/Encoder/EncoderDetector.php
+++ b/src/Encoder/EncoderDetector.php
@@ -9,7 +9,9 @@
final class EncoderDetector
{
/**
- * @return XmlEncoder
+ * @return XmlEncoder
+ *
+ * @psalm-suppress InvalidReturnType, PossiblyInvalidArgument, InvalidReturnStatement - The simple type detector could return string|null, but should not be an issue here.
*/
public function __invoke(Context $context): XmlEncoder
{
@@ -29,7 +31,7 @@ public function __invoke(Context $context): XmlEncoder
}
/**
- * @return XmlEncoder
+ * @return XmlEncoder
*/
private function detectComplexTypeEncoder(XsdType $type, Context $context): XmlEncoder
{
@@ -43,7 +45,7 @@ private function detectComplexTypeEncoder(XsdType $type, Context $context): XmlE
// Try to find a match for the extended complex type:
// Or fallback to the default object encoder.
return $meta->extends()
- ->filter(static fn ($extend): bool => !$extend['isSimple'])
+ ->filter(static fn ($extend): bool => !($extend['isSimple'] ?? false))
->map(static fn ($extends) : XmlEncoder => $context->registry->findComplexEncoderByNamespaceName(
$extends['namespace'],
$extends['type'],
diff --git a/src/Encoder/ErrorHandlingEncoder.php b/src/Encoder/ErrorHandlingEncoder.php
index b730a16..2dca5df 100644
--- a/src/Encoder/ErrorHandlingEncoder.php
+++ b/src/Encoder/ErrorHandlingEncoder.php
@@ -8,26 +8,29 @@
use VeeWee\Reflecta\Iso\Iso;
/**
- * @template I
- * @template O
+ * @template-covariant TData
+ * @template-covariant TXml
*
- * @implements XmlEncoder
+ * @implements XmlEncoder
*
*/
final class ErrorHandlingEncoder implements XmlEncoder
{
/**
- * @param XmlEncoder $encoder
+ * @param XmlEncoder $encoder
*/
public function __construct(
private readonly XmlEncoder $encoder
) {
}
+ /**
+ * @return Iso
+ */
public function iso(Context $context): Iso
{
$innerIso = $this->encoder->iso($context);
- $buildPath = static function() use ($context): ?string {
+ $buildPath = static function () use ($context): ?string {
$meta = $context->type->getMeta();
$isElement = $meta->isElement()->unwrapOr(false);
$isAttribute = $meta->isAttribute()->unwrapOr(false);
@@ -44,6 +47,10 @@ public function iso(Context $context): Iso
};
return new Iso(
+ /**
+ * @psalm-param TData $value
+ * @psalm-return TXml
+ */
static function (mixed $value) use ($innerIso, $context, $buildPath): mixed {
try {
return $innerIso->to($value);
@@ -51,6 +58,10 @@ static function (mixed $value) use ($innerIso, $context, $buildPath): mixed {
throw EncodingException::encodingValue($value, $context->type, $exception, $buildPath());
}
},
+ /**
+ * @psalm-param TXml $value
+ * @psalm-return TData
+ */
static function (mixed $value) use ($innerIso, $context, $buildPath): mixed {
try {
return $innerIso->from($value);
diff --git a/src/Encoder/ObjectEncoder.php b/src/Encoder/ObjectEncoder.php
index 125ab6d..99f29b4 100644
--- a/src/Encoder/ObjectEncoder.php
+++ b/src/Encoder/ObjectEncoder.php
@@ -17,7 +17,6 @@
use VeeWee\Reflecta\Lens\Lens;
use function Psl\Dict\map;
use function Psl\Dict\reindex;
-use function Psl\invariant;
use function Psl\Iter\any;
use function Psl\Vec\sort_by;
use function VeeWee\Reflecta\Iso\object_data;
@@ -29,26 +28,39 @@
use function VeeWee\Xml\Writer\Builder\value as buildValue;
/**
- * @implements XmlEncoder
+ * @template TObj extends object
+ *
+ * @implements XmlEncoder
*/
final class ObjectEncoder implements XmlEncoder
{
/**
- * @param class-string