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 $className + * @param class-string $className */ public function __construct( private readonly string $className ) { } + /** + * @return Iso + */ public function iso(Context $context): Iso { $properties = $this->detectProperties($context); return new Iso( + /** + * @param TObj|array $value + * @return non-empty-string + */ function (object|array $value) use ($context, $properties) : string { return $this->to($context, $properties, $value); }, + /** + * @param non-empty-string $value + * @return TObj + */ function (string $value) use ($context, $properties) : object { return $this->from($context, $properties, $value); } @@ -56,7 +68,10 @@ function (string $value) use ($context, $properties) : object { } /** + * @param TObj|array $data * @param array $properties + * + * @return non-empty-string */ private function to(Context $context, array $properties, object|array $data): string { @@ -83,6 +98,10 @@ private function to(Context $context, array $properties, object|array $data): st function (Property $property) use ($context, $data, $defaultAction) : Closure { $type = $property->getType(); $lens = $this->decorateLensForType(property($property->getName()), $type); + /** + * @psalm-var mixed $value + * @psalm-suppress PossiblyInvalidArgument - Psalm gets lost in the lens. + */ $value = $lens ->tryGet($data) ->catch(static fn () => null) @@ -91,7 +110,6 @@ function (Property $property) use ($context, $data, $defaultAction) : Closure { return $this->handleProperty( $property, onAttribute: fn (): Closure => $value ? (new AttributeBuilder( - $context, $type, $this->grabIsoForProperty($context, $property)->to($value) ))(...) : $defaultAction, @@ -109,6 +127,9 @@ function (Property $property) use ($context, $data, $defaultAction) : Closure { /** * @param array $properties + * @param non-empty-string $data + * + * @return TObj */ private function from(Context $context, array $properties, string $data): object { @@ -121,6 +142,7 @@ function (Property $property) use ($context, $nodes): mixed { $type = $property->getType(); $meta = $type->getMeta(); $isList = $meta->isList()->unwrapOr(false); + /** @psalm-var string|null $value */ $value = $this->decorateLensForType( index($property->getName()), $type @@ -132,7 +154,7 @@ function (Property $property) use ($context, $nodes): mixed { return $this->handleProperty( $property, - onAttribute: fn (): mixed => $this->grabIsoForProperty($context, $property)->from($value), + onAttribute: fn (): mixed => /** @psalm-suppress PossiblyNullArgument */$this->grabIsoForProperty($context, $property)->from($value), onValue: fn (): mixed => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue, onElements: fn (): mixed => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue, ); @@ -141,6 +163,9 @@ function (Property $property) use ($context, $nodes): mixed { ); } + /** + * @return Iso + */ private function grabIsoForProperty(Context $context, Property $property): Iso { $propertyContext = $context->withType($property->getType()); @@ -150,12 +175,12 @@ private function grabIsoForProperty(Context $context, Property $property): Iso } /** - * @template T + * @template X * - * @param Closure(): T $onAttribute - * @param Closure(): T $onValue - * @param Closure(): T $onElements - * @return T + * @param Closure(): X $onAttribute + * @param Closure(): X $onValue + * @param Closure(): X $onElements + * @return X */ private function handleProperty( Property $property, @@ -173,9 +198,12 @@ private function handleProperty( } /** - * @param Lens $lens + * @template S + * @template A + * + * @param Lens $lens * - * @return Lens + * @return Lens */ private function decorateLensForType(Lens $lens, XsdType $type): Lens { diff --git a/src/Encoder/OptionalElementEncoder.php b/src/Encoder/OptionalElementEncoder.php index 3314063..c78ab2e 100644 --- a/src/Encoder/OptionalElementEncoder.php +++ b/src/Encoder/OptionalElementEncoder.php @@ -11,12 +11,12 @@ /** * @template T of mixed - * @implements XmlEncoder + * @implements XmlEncoder */ final class OptionalElementEncoder implements XmlEncoder { /** - * @param XmlEncoder $elementEncoder + * @param XmlEncoder $elementEncoder */ public function __construct( private readonly XmlEncoder $elementEncoder @@ -24,7 +24,7 @@ public function __construct( } /** - * @return Iso + * @return Iso */ public function iso(Context $context): Iso { @@ -41,11 +41,17 @@ public function iso(Context $context): Iso $elementIso = $this->elementEncoder->iso($context); return new Iso( + /** + * @param T|null $raw + */ static fn (mixed $raw): string => match (true) { $raw === null && $isNillable => (new XsdTypeXmlElementWriter())($context, new NilAttributeBuilder()), $raw === null => '', default => $elementIso->to($raw), }, + /** + * @return T|null + */ static function (string $xml) use ($elementIso) : mixed { if ($xml === '') { return null; diff --git a/src/Encoder/RepeatingElementEncoder.php b/src/Encoder/RepeatingElementEncoder.php index f019177..c92933e 100644 --- a/src/Encoder/RepeatingElementEncoder.php +++ b/src/Encoder/RepeatingElementEncoder.php @@ -13,18 +13,21 @@ /** * @template T - * @implements XmlEncoder> + * @implements XmlEncoder, string> */ final class RepeatingElementEncoder implements Feature\ListAware, XmlEncoder { /** - * @param XmlEncoder $typeEncoder + * @param XmlEncoder $typeEncoder */ public function __construct( private readonly XmlEncoder $typeEncoder ) { } + /** + * @return Iso, string> + */ public function iso(Context $context): Iso { $type = $context->type; @@ -43,6 +46,9 @@ static function (iterable $raw) use ($innerIso): string { return join( map( $raw, + /** + * @param T $item + */ static fn (mixed $item): string => $innerIso->to($item) ), '' diff --git a/src/Encoder/SimpleType/AttributeValueEncoder.php b/src/Encoder/SimpleType/AttributeValueEncoder.php index 945aac1..5bc702f 100644 --- a/src/Encoder/SimpleType/AttributeValueEncoder.php +++ b/src/Encoder/SimpleType/AttributeValueEncoder.php @@ -7,14 +7,15 @@ use Soap\Encoding\Encoder\XmlEncoder; use Soap\Encoding\Exception\RestrictionException; use VeeWee\Reflecta\Iso\Iso; +use function Psl\Type\scalar; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class AttributeValueEncoder implements XmlEncoder { /** - * @param XmlEncoder $typeEncoder + * @param XmlEncoder $typeEncoder */ public function __construct( private readonly XmlEncoder $typeEncoder @@ -22,7 +23,7 @@ public function __construct( } /** - * @return Iso + * @return Iso */ public function iso(Context $context): Iso { @@ -40,10 +41,13 @@ public function to(Context $context, mixed $value): ?string ->unwrapOr(null); if ($fixed !== null && $value !== $fixed) { - throw RestrictionException::invalidFixedValue($fixed, $value); + throw RestrictionException::invalidFixedValue( + scalar()->assert($fixed), + scalar()->assert($value) + ); } - return $value ? $this->typeEncoder->iso($context)->to($value) : null; + return $value !== null ? $this->typeEncoder->iso($context)->to($value) : null; } public function from(Context $context, ?string $value): mixed @@ -55,6 +59,6 @@ public function from(Context $context, ?string $value): mixed $meta = $context->type->getMeta(); $default = $meta->fixed()->or($meta->default())->unwrapOr(null); - return $default ? $this->typeEncoder->iso($context)->from($default) : null; + return $default !== null ? $this->typeEncoder->iso($context)->from($default) : null; } } diff --git a/src/Encoder/SimpleType/BackedEnumTypeEncoder.php b/src/Encoder/SimpleType/BackedEnumTypeEncoder.php index fd80b12..c50fe41 100644 --- a/src/Encoder/SimpleType/BackedEnumTypeEncoder.php +++ b/src/Encoder/SimpleType/BackedEnumTypeEncoder.php @@ -10,31 +10,28 @@ use function Psl\Type\backed_enum; /** - * @template T of \BackedEnum - * - * @implements XmlEncoder + * @implements XmlEncoder */ final class BackedEnumTypeEncoder implements XmlEncoder { /** - * @param enum-string $enumClass + * @param class-string $enumClass */ public function __construct( private readonly string $enumClass ) { } + /** + * @return Iso + */ public function iso(Context $context): Iso { - return (new Iso( - /** - * @template T $value - */ - static fn (BackedEnum $enum): string => $enum->value, - /** - * @return T - */ - fn (string $value): BackedEnum => backed_enum($this->enumClass)->coerce($value), - )); + return ( + new Iso( + static fn (BackedEnum $enum): string => (string) $enum->value, + fn (string $value): BackedEnum => backed_enum($this->enumClass)->coerce($value), + ) + ); } } diff --git a/src/Encoder/SimpleType/Base64BinaryTypeEncoder.php b/src/Encoder/SimpleType/Base64BinaryTypeEncoder.php index 9992c94..85d9a85 100644 --- a/src/Encoder/SimpleType/Base64BinaryTypeEncoder.php +++ b/src/Encoder/SimpleType/Base64BinaryTypeEncoder.php @@ -13,6 +13,9 @@ */ final class Base64BinaryTypeEncoder implements XmlEncoder { + /** + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SimpleType/BoolTypeEncoder.php b/src/Encoder/SimpleType/BoolTypeEncoder.php index f9028f9..2fa2316 100644 --- a/src/Encoder/SimpleType/BoolTypeEncoder.php +++ b/src/Encoder/SimpleType/BoolTypeEncoder.php @@ -8,10 +8,13 @@ use VeeWee\Reflecta\Iso\Iso; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class BoolTypeEncoder implements XmlEncoder { + /** + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SimpleType/DateTimeTypeEncoder.php b/src/Encoder/SimpleType/DateTimeTypeEncoder.php index 5323431..c5bfa61 100644 --- a/src/Encoder/SimpleType/DateTimeTypeEncoder.php +++ b/src/Encoder/SimpleType/DateTimeTypeEncoder.php @@ -10,7 +10,7 @@ use VeeWee\Reflecta\Iso\Iso; /** - * @implements XmlEncoder + * @implements XmlEncoder<\DateTimeInterface, string> */ final class DateTimeTypeEncoder implements XmlEncoder { @@ -29,6 +29,7 @@ public static function default(): self public static function local(): self { + /** @psalm-var DateTimeTypeEncoder $instance */ static $instance = new self(self::DATE_FORMAT_LOCAL); return $instance; @@ -36,13 +37,14 @@ public static function local(): self public static function timeZoned(): self { + /** @psalm-var DateTimeTypeEncoder $instance */ static $instance = new self(self::DATE_FORMAT_TIME_ZONED); return $instance; } /** - * @return Iso + * @return Iso */ public function iso(Context $context): Iso { diff --git a/src/Encoder/SimpleType/DateTypeEncoder.php b/src/Encoder/SimpleType/DateTypeEncoder.php index 051870f..d0ff0c8 100644 --- a/src/Encoder/SimpleType/DateTypeEncoder.php +++ b/src/Encoder/SimpleType/DateTypeEncoder.php @@ -10,7 +10,7 @@ use VeeWee\Reflecta\Iso\Iso; /** - * @implements XmlEncoder + * @implements XmlEncoder<\DateTimeInterface, string> */ final class DateTypeEncoder implements XmlEncoder { @@ -29,6 +29,7 @@ public static function default(): self public static function local(): self { + /** @psalm-var DateTypeEncoder $instance */ static $instance = new self(self::DATE_FORMAT_LOCAL); return $instance; @@ -36,13 +37,14 @@ public static function local(): self public static function timeZoned(): self { + /** @psalm-var DateTypeEncoder $instance */ static $instance = new self(self::DATE_FORMAT_TIME_ZONED); return $instance; } /** - * @return Iso + * @return Iso */ public function iso(Context $context): Iso { diff --git a/src/Encoder/SimpleType/EncoderDetector.php b/src/Encoder/SimpleType/EncoderDetector.php index d36f361..41a8e60 100644 --- a/src/Encoder/SimpleType/EncoderDetector.php +++ b/src/Encoder/SimpleType/EncoderDetector.php @@ -15,7 +15,7 @@ final class EncoderDetector { /** - * @return XmlEncoder + * @return XmlEncoder */ public function __invoke(Context $context): XmlEncoder { @@ -45,7 +45,7 @@ public function __invoke(Context $context): XmlEncoder } /** - * @return XmlEncoder + * @return XmlEncoder */ private function detectSimpleTypeEncoder(XsdType $type, Context $context): XmlEncoder { @@ -59,12 +59,12 @@ private function detectSimpleTypeEncoder(XsdType $type, Context $context): XmlEn // Try to find a match for the extended simple type: // Or fallback to the default scalar 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->findSimpleEncoderByNamespaceName( $extends['namespace'], $extends['type'], )) - ->unwrapOr(ScalarTypeEncoder::static()); + ->unwrapOr(ScalarTypeEncoder::default()); } private function detectIsListType(XsdType $type): bool diff --git a/src/Encoder/SimpleType/FloatTypeEncoder.php b/src/Encoder/SimpleType/FloatTypeEncoder.php index 2bfa1ef..d5c1d8a 100644 --- a/src/Encoder/SimpleType/FloatTypeEncoder.php +++ b/src/Encoder/SimpleType/FloatTypeEncoder.php @@ -10,10 +10,13 @@ use function Psl\Type\numeric_string; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class FloatTypeEncoder implements XmlEncoder { + /** + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SimpleType/HexBinaryTypeEncoder.php b/src/Encoder/SimpleType/HexBinaryTypeEncoder.php index 2c67b2d..3cceadf 100644 --- a/src/Encoder/SimpleType/HexBinaryTypeEncoder.php +++ b/src/Encoder/SimpleType/HexBinaryTypeEncoder.php @@ -13,6 +13,9 @@ */ final class HexBinaryTypeEncoder implements XmlEncoder { + /** + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SimpleType/IntTypeEncoder.php b/src/Encoder/SimpleType/IntTypeEncoder.php index 2ead0e3..8ebafe5 100644 --- a/src/Encoder/SimpleType/IntTypeEncoder.php +++ b/src/Encoder/SimpleType/IntTypeEncoder.php @@ -10,10 +10,13 @@ use function Psl\Type\string; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class IntTypeEncoder implements XmlEncoder { + /** + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SimpleType/NullableEncoder.php b/src/Encoder/SimpleType/NullableEncoder.php deleted file mode 100644 index ec9417f..0000000 --- a/src/Encoder/SimpleType/NullableEncoder.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ -final class NullableEncoder implements XmlEncoder -{ - /** - * @param XmlEncoder $typeEncoder - */ - public function __construct( - private readonly XmlEncoder $typeEncoder - ) { - } - - - /** - * @return Iso - */ - public function iso(Context $context): Iso - { - $type = $context->type; - $meta = $type->getMeta(); - $typeEncoder = $this->typeEncoder->iso($context); - - $isNullable = $meta->isNullable()->unwrapOr(false); - if (!$isNullable) { - return $typeEncoder; - } - - return (new Iso( - /** - * @param T $value - */ - static fn (mixed $value): ?string => $value === null ? null : $typeEncoder->to($value), - /** - * @return T - */ - static fn (?string $value): mixed => ($value === null) ? null : $typeEncoder->from($value) - )); - } -} diff --git a/src/Encoder/SimpleType/ScalarTypeEncoder.php b/src/Encoder/SimpleType/ScalarTypeEncoder.php index 9cd274f..fc76970 100644 --- a/src/Encoder/SimpleType/ScalarTypeEncoder.php +++ b/src/Encoder/SimpleType/ScalarTypeEncoder.php @@ -4,25 +4,29 @@ namespace Soap\Encoding\Encoder\SimpleType; use Psl\Type; -use RuntimeException; use Soap\Encoding\Encoder\Context; use Soap\Encoding\Encoder\XmlEncoder; -use Soap\Encoding\Exception\InvalidArgumentException; use Soap\Encoding\Exception\RestrictionException; use VeeWee\Reflecta\Iso\Iso; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class ScalarTypeEncoder implements XmlEncoder { - public static function static(): self + public static function default(): self { + /** @psalm-var ScalarTypeEncoder $instance */ static $instance = new self(); return $instance; } + /** + * Will parse scalar values but accepts mixed to throw exceptions on invalid types. + * + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SimpleType/SimpleListEncoder.php b/src/Encoder/SimpleType/SimpleListEncoder.php index 1896850..f6a1fcb 100644 --- a/src/Encoder/SimpleType/SimpleListEncoder.php +++ b/src/Encoder/SimpleType/SimpleListEncoder.php @@ -13,12 +13,12 @@ use function Psl\Vec\map; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class SimpleListEncoder implements Feature\ListAware, XmlEncoder { /** - * @param XmlEncoder $typeEncoder + * @param XmlEncoder $typeEncoder */ public function __construct( private readonly XmlEncoder $typeEncoder @@ -26,7 +26,9 @@ public function __construct( } /** - * @return Iso + * @psalm-suppress ImplementedReturnTypeMismatch - ISO does not ISO + * + * @return Iso */ public function iso(Context $context): Iso { diff --git a/src/Encoder/SimpleType/StringTypeEncoder.php b/src/Encoder/SimpleType/StringTypeEncoder.php index 3dad42e..a34d75b 100644 --- a/src/Encoder/SimpleType/StringTypeEncoder.php +++ b/src/Encoder/SimpleType/StringTypeEncoder.php @@ -13,6 +13,9 @@ */ final class StringTypeEncoder implements XmlEncoder { + /** + * @return Iso + */ public function iso(Context $context): Iso { return (new Iso( diff --git a/src/Encoder/SoapEnc/ApacheMapEncoder.php b/src/Encoder/SoapEnc/ApacheMapEncoder.php index bb56e51..bfebc3b 100644 --- a/src/Encoder/SoapEnc/ApacheMapEncoder.php +++ b/src/Encoder/SoapEnc/ApacheMapEncoder.php @@ -24,23 +24,30 @@ use function VeeWee\Xml\Writer\Builder\value as buildValue; /** - * @template T - * - * @implements XmlEncoder> + * @implements XmlEncoder, non-empty-string> */ final class ApacheMapEncoder implements XmlEncoder { /** - * @return Iso> + * @return Iso, non-empty-string> */ public function iso(Context $context): Iso { return (new Iso( + /** + * @return non-empty-string + */ fn (array $value): string => $this->encodeArray($context, $value), + /** + * @param non-empty-string $value + */ fn (string $value): array => $this->decodeArray($context, $value), )); } + /** + * @return non-empty-string + */ private function encodeArray(Context $context, array $data): string { $anyContext = $context->withType(XsdType::any()); @@ -56,11 +63,11 @@ private function encodeArray(Context $context, array $data): string buildChildren([ element('key', buildChildren([ (new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $key))), - buildValue(ScalarTypeEncoder::static()->iso($context)->to($key)) + buildValue(ScalarTypeEncoder::default()->iso($context)->to($key)) ])), element('value', buildChildren([ (new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $value))), - buildValue(ScalarTypeEncoder::static()->iso($context)->to($value)) + buildValue(ScalarTypeEncoder::default()->iso($context)->to($value)) ])), ]), ) @@ -69,6 +76,9 @@ private function encodeArray(Context $context, array $data): string ); } + /** + * @param non-empty-string $value + */ private function decodeArray(Context $context, string $value): array { $document = Document::fromXmlString($value); @@ -78,9 +88,10 @@ private function decodeArray(Context $context, string $value): array return readChildren($element)->reduce( static function (array $map, DOMElement $item) use ($context, $xpath): array { $key = $xpath->evaluate('string(./key)', string(), $item); + /** @psalm-var mixed $value */ $value = (new ElementValueReader())( $context->withType(XsdType::any()), - ScalarTypeEncoder::static(), + ScalarTypeEncoder::default(), assert_element($xpath->querySingle('./value', $item)) ); diff --git a/src/Encoder/SoapEnc/SoapArrayEncoder.php b/src/Encoder/SoapEnc/SoapArrayEncoder.php index d519229..295905f 100644 --- a/src/Encoder/SoapEnc/SoapArrayEncoder.php +++ b/src/Encoder/SoapEnc/SoapArrayEncoder.php @@ -5,6 +5,7 @@ use Closure; use DOMElement; +use Generator; use Soap\Encoding\Encoder\Context; use Soap\Encoding\Encoder\Feature\ListAware; use Soap\Encoding\Encoder\SimpleType\ScalarTypeEncoder; @@ -18,6 +19,7 @@ use Soap\WsdlReader\Parser\Xml\QnameParser; use VeeWee\Reflecta\Iso\Iso; use VeeWee\Xml\Dom\Document; +use XMLWriter; use function Psl\Vec\map; use function VeeWee\Xml\Dom\Locator\Element\children as readChildren; use function VeeWee\Xml\Writer\Builder\children; @@ -27,25 +29,33 @@ use function VeeWee\Xml\Writer\Builder\value as buildValue; /** - * @template T - * - * @implements XmlEncoder> + * @implements XmlEncoder, non-empty-string> */ final class SoapArrayEncoder implements ListAware, XmlEncoder { /** - * @return Iso> + * @return Iso, non-empty-string> */ public function iso(Context $context): Iso { return (new Iso( + /** + * @param list $value + * @return non-empty-string + */ fn (array $value): string => $this->encodeArray($context, $value), + /** + * @param non-empty-string $value + * @return list + */ fn (string $value): array => $this->decodeArray($context, $value), )); } /** - * @param list $data + * @param list $data + * + * @return non-empty-string */ private function encodeArray(Context $context, array $data): string { @@ -85,11 +95,15 @@ private function encodeArray(Context $context, array $data): string ); } + /** + * @psalm-param mixed $value + * @return Closure(XMLWriter): Generator + */ private function itemElement(Context $context, ?string $itemNodeName, string $itemType, mixed $value): Closure { - $buildValue = buildValue(ScalarTypeEncoder::static()->iso($context)->to($value)); + $buildValue = buildValue(ScalarTypeEncoder::default()->iso($context)->to($value)); - if ($context->bindingUse === BindingUse::ENCODED || $itemNodeName) { + if ($context->bindingUse === BindingUse::ENCODED || $itemNodeName !== null) { return element( $itemNodeName ?? 'item', children([ @@ -109,6 +123,10 @@ private function itemElement(Context $context, ?string $itemNodeName, string $it ); } + /** + * @param non-empty-string $value + * @return list + */ private function decodeArray(Context $context, string $value): array { $document = Document::fromXmlString($value); @@ -120,9 +138,10 @@ private function decodeArray(Context $context, string $value): array * @return list */ static function (array $list, DOMElement $item) use ($context): array { + /** @psalm-var mixed $value */ $value = (new ElementValueReader())( $context->withType(XsdType::any()), - ScalarTypeEncoder::static(), + ScalarTypeEncoder::default(), $item ); diff --git a/src/Encoder/SoapEnc/SoapObjectEncoder.php b/src/Encoder/SoapEnc/SoapObjectEncoder.php index 2862599..fa84c70 100644 --- a/src/Encoder/SoapEnc/SoapObjectEncoder.php +++ b/src/Encoder/SoapEnc/SoapObjectEncoder.php @@ -22,24 +22,32 @@ use function VeeWee\Xml\Writer\Builder\value as buildValue; /** - * @implements XmlEncoder + * @implements XmlEncoder */ final class SoapObjectEncoder implements XmlEncoder { /** - * @return Iso + * @return Iso */ public function iso(Context $context): Iso { return (new Iso( + /** + * @return non-empty-string + */ fn (object $value): string => $this->encodeArray($context, $value), + /** + * @param non-empty-string $value + */ fn (string $value): object => $this->decodeArray($context, $value), )); } + /** + * @return non-empty-string + */ private function encodeArray(Context $context, object $data): string { - $type = $context->type; $anyContext = $context->withType(XsdType::any()); return (new XsdTypeXmlElementWriter())( @@ -49,10 +57,10 @@ private function encodeArray(Context $context, object $data): string ...\Psl\Vec\map_with_key( (array) $data, static fn (mixed $key, mixed $value): Closure => element( - $key, + (string) $key, children([ (new XsiAttributeBuilder($anyContext, XsiTypeDetector::detectFromValue($anyContext, $value))), - buildValue(ScalarTypeEncoder::static()->iso($context)->to($value)) + buildValue(ScalarTypeEncoder::default()->iso($context)->to($value)) ]), ) ) @@ -60,6 +68,9 @@ private function encodeArray(Context $context, object $data): string ); } + /** + * @param non-empty-string $value + */ private function decodeArray(Context $context, string $value): object { $document = Document::fromXmlString($value); @@ -67,10 +78,11 @@ private function decodeArray(Context $context, string $value): object return (object) readChildren($element)->reduce( static function (array $map, DOMElement $item) use ($context): array { - $key = $item->localName; + $key = $item->localName ?? 'unkown'; + /** @psalm-var mixed $value */ $value = (new ElementValueReader())( $context->withType(XsdType::any()), - ScalarTypeEncoder::static(), + ScalarTypeEncoder::default(), $item ); diff --git a/src/Encoder/XmlEncoder.php b/src/Encoder/XmlEncoder.php index 94d0ba2..71e1508 100644 --- a/src/Encoder/XmlEncoder.php +++ b/src/Encoder/XmlEncoder.php @@ -7,13 +7,13 @@ use VeeWee\Reflecta\Iso\Iso; /** - * @template I - * @template O + * @template-covariant TData + * @template-covariant TXml */ interface XmlEncoder { /** - * @return Iso + * @return Iso */ public function iso(Context $context): Iso; } diff --git a/src/EncoderRegistry.php b/src/EncoderRegistry.php index 271a6d0..12fa48c 100644 --- a/src/EncoderRegistry.php +++ b/src/EncoderRegistry.php @@ -19,13 +19,12 @@ use Soap\Xml\Xmlns; use stdClass; use function Psl\Dict\pull; -use function Psl\Vec\map; final class EncoderRegistry { /** - * @param MutableMap $simpleTypeMap - * @param MutableMap $complextTypeMap + * @param MutableMap> $simpleTypeMap + * @param MutableMap> $complextTypeMap */ private function __construct( private MutableMap $simpleTypeMap, @@ -33,6 +32,9 @@ private function __construct( ) { } + /** + * @psalm-suppress InvalidArgument - It is not able to infer the underlying encoder generic types. + */ public static function default(): self { $qNameFormatter = new QNameFormatter(); @@ -40,6 +42,7 @@ public static function default(): self $xsd1999 = Xmlns::xsd1999()->value(); return new self( + /** @var MutableMap> */ new MutableMap([ // Strings: $qNameFormatter($xsd, 'string') => new SimpleType\StringTypeEncoder(), @@ -134,6 +137,7 @@ public static function default(): self $qNameFormatter($xsd1999, 'date') => SimpleType\DateTypeEncoder::default(), $qNameFormatter($xsd1999, 'time') => new SimpleType\StringTypeEncoder(), ]), + /** @var MutableMap> */ new MutableMap([ // SOAP 1.1 ENC $qNameFormatter(EncodingStyle::SOAP_11->value, 'Array') => new SoapEnc\SoapArrayEncoder(), @@ -161,7 +165,6 @@ public static function default(): self * @param non-empty-string $namespace * @param non-empty-string $name * @param class-string $class - * @return $this */ public function addClassMap(string $namespace, string $name, string $class): self { @@ -174,10 +177,11 @@ public function addClassMap(string $namespace, string $name, string $class): sel } /** + * @template T of \BackedEnum + * * @param non-empty-string $namespace * @param non-empty-string $name - * @param enum-class $enumClass - * @return $this + * @param enum-string $enumClass */ public function addBackedEnum(string $namespace, string $name, string $enumClass): self { @@ -192,7 +196,7 @@ public function addBackedEnum(string $namespace, string $name, string $enumClass /** * @param non-empty-string $namespace * @param non-empty-string $name - * @return $this + * @param XmlEncoder $encoder */ public function addSimpleTypeConverter(string $namespace, string $name, XmlEncoder $encoder): self { @@ -207,7 +211,7 @@ public function addSimpleTypeConverter(string $namespace, string $name, XmlEncod /** * @param non-empty-string $namespace * @param non-empty-string $name - * @return $this + * @param XmlEncoder $encoder */ public function addComplexTypeConverter(string $namespace, string $name, XmlEncoder $encoder): self { @@ -220,7 +224,7 @@ public function addComplexTypeConverter(string $namespace, string $name, XmlEnco } /** - * @return XmlEncoder + * @return XmlEncoder */ public function findSimpleEncoderByXsdType(XsdType $type): XmlEncoder { @@ -231,9 +235,7 @@ public function findSimpleEncoderByXsdType(XsdType $type): XmlEncoder } /** - * @param non-empty-string $namespace - * @param non-empty-string $name - * @return XmlEncoder + * @return XmlEncoder */ public function findSimpleEncoderByNamespaceName(string $namespace, string $name): XmlEncoder { @@ -244,7 +246,7 @@ public function findSimpleEncoderByNamespaceName(string $namespace, string $name return $found; } - return new SimpleType\ScalarTypeEncoder(); + return SimpleType\ScalarTypeEncoder::default(); } public function hasRegisteredSimpleTypeForXsdType(XsdType $type): bool @@ -258,7 +260,7 @@ public function hasRegisteredSimpleTypeForXsdType(XsdType $type): bool } /** - * @return XmlEncoder + * @return XmlEncoder */ public function findComplexEncoderByXsdType(XsdType $type): XmlEncoder { @@ -269,9 +271,7 @@ public function findComplexEncoderByXsdType(XsdType $type): XmlEncoder } /** - * @param non-empty-string $namespace - * @param non-empty-string $name - * @return XmlEncoder + * @return XmlEncoder */ public function findComplexEncoderByNamespaceName(string $namespace, string $name): XmlEncoder { @@ -298,7 +298,7 @@ public function hasRegisteredComplexTypeForXsdType(XsdType $type): bool } /** - * @return XmlEncoder + * @return XmlEncoder */ public function detectEncoderForContext(Context $context): XmlEncoder { diff --git a/src/Exception/EncodingException.php b/src/Exception/EncodingException.php index 5979956..7295a92 100644 --- a/src/Exception/EncodingException.php +++ b/src/Exception/EncodingException.php @@ -35,6 +35,9 @@ private function __construct(string $message, array $paths, Throwable $previous) $this->paths = $paths; } + /** + * @psalm-param mixed $value + */ public static function encodingValue( mixed $value, XsdType $expectedType, @@ -54,6 +57,9 @@ public static function encodingValue( ); } + /** + * @psalm-param mixed $value + */ public static function decodingValue( mixed $value, XsdType $expectedType, @@ -73,6 +79,9 @@ public static function decodingValue( ); } + /** + * @return list + */ public function getPaths(): array { return $this->paths; diff --git a/src/Exception/RestrictionException.php b/src/Exception/RestrictionException.php index b50571f..7f38250 100644 --- a/src/Exception/RestrictionException.php +++ b/src/Exception/RestrictionException.php @@ -4,9 +4,9 @@ namespace Soap\Encoding\Exception; use InvalidArgumentException; +use Psl\Str; use Soap\Encoding\Formatter\QNameFormatter; use Soap\Engine\Metadata\Model\XsdType; -use Psl\Str; final class RestrictionException extends InvalidArgumentException implements ExceptionInterface { @@ -16,9 +16,12 @@ final class RestrictionException extends InvalidArgumentException implements Exc */ public static function invalidFixedValue(mixed $fixedValue, mixed $value): self { - return new self(sprintf('Provided attribute value should be fixed to %s. Got %s', $fixedValue, $value)); + return new self(sprintf('Provided attribute value should be fixed to %s. Got %s', (string) $fixedValue, (string) $value)); } + /** + * @psalm-param mixed $value + */ public static function unsupportedValueType(XsdType $type, mixed $value): self { return new self( @@ -30,13 +33,17 @@ public static function unsupportedValueType(XsdType $type, mixed $value): self ); } + /** + * @param list $supportedValues + * @psalm-param mixed $value + */ public static function unexpectedEnumType(XsdType $type, array $supportedValues, mixed $value): self { return new self( Str\format( 'Unexpected enum value for type %s: %s. Supported values are: %s', (new QNameFormatter())($type->getXmlNamespace(), $type->getXmlTypeName()), - $value, + is_scalar($value) ? (string)$value : get_debug_type($value), Str\join($supportedValues, ', ') ) ); diff --git a/src/TypeInference/ComplexTypeBuilder.php b/src/TypeInference/ComplexTypeBuilder.php index df98e15..5da4fec 100644 --- a/src/TypeInference/ComplexTypeBuilder.php +++ b/src/TypeInference/ComplexTypeBuilder.php @@ -39,7 +39,7 @@ private function detectExtensions(Context $context, Type $type): array $typeMeta = $type->getXsdType()->getMeta(); $extends = $typeMeta->extends() - ->filter(static fn (array $extends): bool => !$extends['isSimple']) + ->filter(static fn (array $extends): bool => !($extends['isSimple'] ?? false)) ->map(static fn (array $extends): Type => $allTypes->fetchByNameAndXmlNamespace($extends['type'], $extends['namespace'])); return $extends diff --git a/src/TypeInference/XsiTypeDetector.php b/src/TypeInference/XsiTypeDetector.php index 66fa515..c10e170 100644 --- a/src/TypeInference/XsiTypeDetector.php +++ b/src/TypeInference/XsiTypeDetector.php @@ -16,6 +16,9 @@ final class XsiTypeDetector { + /** + * @psalm-param mixed $value + */ public static function detectFromValue(Context $context, mixed $value): string { return self::detectFromContext($context)->unwrapOrElse( @@ -34,7 +37,7 @@ static function () use ($context, $value) { } /** - * @return Option> + * @return Option> */ public static function detectEncoderFromXmlElement(Context $context, DOMElement $element): Option { @@ -48,7 +51,7 @@ public static function detectEncoderFromXmlElement(Context $context, DOMElement } [$prefix, $localName] = (new QnameParser)($xsiType); - if (!$prefix) { + if (!$prefix || !$localName) { return none(); } @@ -58,6 +61,10 @@ public static function detectEncoderFromXmlElement(Context $context, DOMElement } $namespaceUri = $namespace->unwrap(); + if (!$namespaceUri) { + return none(); + } + $type = $context->type; $meta = $type->getMeta(); @@ -70,7 +77,7 @@ public static function detectEncoderFromXmlElement(Context $context, DOMElement } /** - * @return Option + * @return Option */ private static function detectFromContext(Context $context): Option { diff --git a/src/Xml/Reader/ChildrenReader.php b/src/Xml/Reader/ChildrenReader.php index 5886075..abb6a27 100644 --- a/src/Xml/Reader/ChildrenReader.php +++ b/src/Xml/Reader/ChildrenReader.php @@ -10,6 +10,9 @@ final class ChildrenReader { + /** + * @param non-empty-string $xml + */ public function __invoke(string $xml): string { $document = Document::fromXmlString($xml); diff --git a/src/Xml/Reader/DocumentToLookupArrayReader.php b/src/Xml/Reader/DocumentToLookupArrayReader.php index 1d7374f..f321af2 100644 --- a/src/Xml/Reader/DocumentToLookupArrayReader.php +++ b/src/Xml/Reader/DocumentToLookupArrayReader.php @@ -10,6 +10,7 @@ final class DocumentToLookupArrayReader { /** + * @param non-empty-string $xml * @return array */ public function __invoke(string $xml): array @@ -21,8 +22,9 @@ public function __invoke(string $xml): array // Read all child elements. // The key is the name of the elements // The value is the raw XML for those element(s) - foreach ($elements = readChildElements($root) as $element) { - $key = $element->localName; + $elements = readChildElements($root); + foreach ($elements as $element) { + $key = $element->localName ?? 'unknown'; $nodeValue = Document::fromXmlNode($element)->stringifyDocumentElement(); // For list-nodes, a concatenated string of the xml nodes will be generated. $value = array_key_exists($key, $nodes) ? $nodes[$key].$nodeValue : $nodeValue; @@ -38,7 +40,8 @@ public function __invoke(string $xml): array // All attributes also need to be added as key => value pairs. foreach (attributes_list($root) as $attribute) { - $nodes[$attribute->localName] = $attribute->value; + $key = $attribute->localName ?? 'unkown'; + $nodes[$key] = $attribute->value; } return $nodes; diff --git a/src/Xml/Reader/ElementValueReader.php b/src/Xml/Reader/ElementValueReader.php index 3961fad..21b244e 100644 --- a/src/Xml/Reader/ElementValueReader.php +++ b/src/Xml/Reader/ElementValueReader.php @@ -13,11 +13,16 @@ final class ElementValueReader { + /** + * @param XmlEncoder $encoder + * @psalm-return mixed + */ public function __invoke( Context $context, XmlEncoder $encoder, DOMElement $element ): mixed { + /** @var XmlEncoder $encoder */ $encoder = match (true) { $encoder instanceof DisregardXsiInformation => $encoder, default => XsiTypeDetector::detectEncoderFromXmlElement($context, $element)->unwrapOr($encoder) diff --git a/src/Xml/Reader/OperationReader.php b/src/Xml/Reader/OperationReader.php index f560a3d..cca3545 100644 --- a/src/Xml/Reader/OperationReader.php +++ b/src/Xml/Reader/OperationReader.php @@ -21,6 +21,7 @@ public function __construct( /** * Reads all operation response message parts: * + * @param non-empty-string $xml * @return list */ public function __invoke(string $xml): array diff --git a/src/Xml/Reader/SoapEnvelopeReader.php b/src/Xml/Reader/SoapEnvelopeReader.php index e439a87..ea00d77 100644 --- a/src/Xml/Reader/SoapEnvelopeReader.php +++ b/src/Xml/Reader/SoapEnvelopeReader.php @@ -5,13 +5,17 @@ use Soap\Xml\Locator\SoapBodyLocator; use VeeWee\Xml\Dom\Document; +use function VeeWee\Xml\Dom\Assert\assert_element; final class SoapEnvelopeReader { + /** + * @param non-empty-string $xml + */ public function __invoke(string $xml): string { $document = Document::fromXmlString($xml); - $body = $document->locate(new SoapBodyLocator()); + $body = assert_element($document->locate(new SoapBodyLocator())); return (new ChildrenReader())(Document::fromXmlNode($body)->toXmlString()); } diff --git a/src/Xml/Writer/AttributeBuilder.php b/src/Xml/Writer/AttributeBuilder.php index 6737e55..97def5a 100644 --- a/src/Xml/Writer/AttributeBuilder.php +++ b/src/Xml/Writer/AttributeBuilder.php @@ -4,7 +4,6 @@ namespace Soap\Encoding\Xml\Writer; use Generator; -use Soap\Encoding\Encoder\Context; use Soap\Engine\Metadata\Model\XsdType; use VeeWee\Xml\Writer\Builder\Builder; use XMLWriter; @@ -14,7 +13,6 @@ final class AttributeBuilder implements Builder { public function __construct( - private readonly Context $context, private readonly XsdType $type, private readonly string $value ) { diff --git a/src/Xml/Writer/ElementValueBuilder.php b/src/Xml/Writer/ElementValueBuilder.php index 9627a49..12e1f61 100644 --- a/src/Xml/Writer/ElementValueBuilder.php +++ b/src/Xml/Writer/ElementValueBuilder.php @@ -14,6 +14,10 @@ final class ElementValueBuilder { + /** + * @param Iso $iso + * @psalm-param mixed $value + */ public function __construct( private readonly Context $context, private readonly Iso $iso, @@ -22,7 +26,7 @@ public function __construct( } /** - * @return Generator + * @return Generator */ public function __invoke(XMLWriter $writer): Generator { diff --git a/src/Xml/Writer/OperationBuilder.php b/src/Xml/Writer/OperationBuilder.php index dae48b0..9ef9654 100644 --- a/src/Xml/Writer/OperationBuilder.php +++ b/src/Xml/Writer/OperationBuilder.php @@ -16,7 +16,7 @@ final class OperationBuilder { /** - * @param list $parameters + * @param list $parameters */ public function __construct( private readonly MethodMeta $meta, diff --git a/src/Xml/Writer/XsdTypeXmlElementWriter.php b/src/Xml/Writer/XsdTypeXmlElementWriter.php index ebf77d0..f2558c5 100644 --- a/src/Xml/Writer/XsdTypeXmlElementWriter.php +++ b/src/Xml/Writer/XsdTypeXmlElementWriter.php @@ -13,9 +13,12 @@ final class XsdTypeXmlElementWriter { /** * @param callable(XMLWriter): Generator $children + * + * @return non-empty-string */ public function __invoke(Context $context, callable $children): string { + /** @psalm-var non-empty-string */ return Writer::inMemory() ->write(new ElementBuilder($context, $children(...))) ->map(memory_output()); diff --git a/src/Xml/Writer/XsiAttributeBuilder.php b/src/Xml/Writer/XsiAttributeBuilder.php index 02b91e8..6da83ea 100644 --- a/src/Xml/Writer/XsiAttributeBuilder.php +++ b/src/Xml/Writer/XsiAttributeBuilder.php @@ -21,6 +21,9 @@ public function __construct( ) { } + /** + * @return Generator + */ public function __invoke(XMLWriter $writer): Generator { if ($this->context->bindingUse !== BindingUse::ENCODED) { diff --git a/tests/Unit/Encoder/SimpleType/NullableEncoderTest.php b/tests/Unit/Encoder/SimpleType/NullableEncoderTest.php deleted file mode 100644 index 2a4e848..0000000 --- a/tests/Unit/Encoder/SimpleType/NullableEncoderTest.php +++ /dev/null @@ -1,62 +0,0 @@ - $encoder = new NullableEncoder(new StringTypeEncoder()), - 'context' => $context = self::createContext( - XsdType::guess('string') - ->withMeta( - static fn ($meta) => $meta - ->withIsNullable(true) - ) - ), - ]; - - yield 'without-value' => [ - ...$baseConfig, - 'xml' => null, - 'data' => null, - ]; - yield 'with-empty-value' => [ - ...$baseConfig, - 'xml' => '', - 'data' => '', - ]; - yield 'with-value' => [ - ...$baseConfig, - 'xml' => 'hello', - 'data' => 'hello', - ]; - yield 'with-non-nullable-value' => [ - ...$baseConfig, - 'context' => $context->withType( - $context->type->withMeta( - static fn ($meta) => $meta - ->withIsNullable(false) - ) - ), - 'xml' => 'hello', - 'data' => 'hello', - ]; - yield 'with-int-value' => [ - ...$baseConfig, - 'encoder' => new NullableEncoder(new IntTypeEncoder()), - 'xml' => '42', - 'data' => 42, - ]; - } -}