diff --git a/composer.json b/composer.json index 8c8f4509..51337984 100644 --- a/composer.json +++ b/composer.json @@ -47,15 +47,15 @@ "yiisoft/injector": "^1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.93", + "friendsofphp/php-cs-fixer": "^3.95.11", "maglnet/composer-require-checker": "^4.7.1", - "phpbench/phpbench": "^1.4.1", - "phpunit/phpunit": "^10.5.45", - "rector/rector": "^2.0.11", + "phpbench/phpbench": "^1.4.3", + "phpunit/phpunit": "^10.5.63", + "rector/rector": "^2.5.2", "roave/infection-static-analysis-plugin": "^1.35", - "vimeo/psalm": "^5.26.1 || ^6.10", + "vimeo/psalm": "^5.26.1 || ^6.16.1", "yiisoft/code-style": "^1.0", - "yiisoft/test-support": "^3.0.2", + "yiisoft/test-support": "^3.2.0", "yiisoft/yii-debug": "dev-master" }, "suggest": { diff --git a/psalm.xml b/psalm.xml index 3d975f11..a2f03a7f 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ queueProvider - ->get($input->getArgument('queue')) - ->listen(); + $queueName = (string) $input->getArgument('queue'); - return 0; + $this->queueProvider->get($queueName)->listen(); + + return Command::SUCCESS; } } diff --git a/src/Debug/QueueCollector.php b/src/Debug/QueueCollector.php index 7e9ecb1e..cacafe94 100644 --- a/src/Debug/QueueCollector.php +++ b/src/Debug/QueueCollector.php @@ -16,8 +16,15 @@ final class QueueCollector implements SummaryCollectorInterface { use CollectorTrait; + /** + * @var array[] + */ private array $pushes = []; private array $statuses = []; + + /** + * @var array[] + */ private array $processingMessages = []; public function getCollected(): array @@ -72,9 +79,19 @@ public function getSummary(): array return []; } - $countPushes = array_sum(array_map(static fn($messages) => is_countable($messages) ? count($messages) : 0, $this->pushes)); + $countPushes = array_sum( + array_map( + count(...), + $this->pushes, + ), + ); $countStatuses = count($this->statuses); - $countProcessingMessages = array_sum(array_map(static fn($messages) => is_countable($messages) ? count($messages) : 0, $this->processingMessages)); + $countProcessingMessages = array_sum( + array_map( + count(...), + $this->processingMessages, + ), + ); return [ 'countPushes' => $countPushes, diff --git a/src/Message/GenericMessage.php b/src/Message/GenericMessage.php index c988263b..71b880a1 100644 --- a/src/Message/GenericMessage.php +++ b/src/Message/GenericMessage.php @@ -25,7 +25,7 @@ public function __construct( private readonly bool|int|float|string|array|null $payload, ) {} - public static function fromPayload(string $type, bool|int|float|string|array|null $payload): MessageInterface + public static function fromPayload(string $type, bool|int|float|string|array|null $payload): static { return new self($type, $payload); } diff --git a/src/Message/Message.php b/src/Message/Message.php index 5f2d7d5f..28ca533f 100644 --- a/src/Message/Message.php +++ b/src/Message/Message.php @@ -4,10 +4,15 @@ namespace Yiisoft\Queue\Message; +/** + * Base implementation of {@see MessageInterface} providing metadata storage. + * + * @psalm-import-type MessageMeta from MessageInterface + */ abstract class Message implements MessageInterface { /** - * @psalm-var array + * @psalm-var MessageMeta */ private array $meta = []; diff --git a/src/Message/MessageInterface.php b/src/Message/MessageInterface.php index cfb79d14..7956c3da 100644 --- a/src/Message/MessageInterface.php +++ b/src/Message/MessageInterface.php @@ -20,8 +20,10 @@ interface MessageInterface * `int`, `float`, `string`), or arrays composed of the same types recursively. * * @psalm-param MessagePayload $payload + * + * @return static Instance of the called class with the given type and payload. */ - public static function fromPayload(string $type, bool|int|float|string|array|null $payload): self; + public static function fromPayload(string $type, bool|int|float|string|array|null $payload): static; /** * Returns message type. @@ -56,6 +58,8 @@ public function getMeta(): array; * @param array $meta Metadata containing only `null`, scalars (`bool`, * `int`, `float`, `string`), or arrays composed of the same types recursively. * + * @return static New instance with the given metadata. + * * @psalm-param MessageMeta $meta */ public function withMeta(array $meta): static; diff --git a/src/Message/Serializer/MessageEncoderInterface.php b/src/Message/Serializer/MessageEncoderInterface.php index 5ee6b51a..94ed3b0b 100644 --- a/src/Message/Serializer/MessageEncoderInterface.php +++ b/src/Message/Serializer/MessageEncoderInterface.php @@ -12,7 +12,10 @@ interface MessageEncoderInterface /** * Encodes a data array into a string representation. * - * @param array $data Data to encode. Contains only scalars, nulls, and arrays — no objects or resources including array contents. + * @param array $data Data to encode. Contains only scalars, nulls, and arrays — no objects or resources including + * array contents. + * + * @return string Encoded string. * * @throws MessageSerializerException If encoding fails. */ diff --git a/src/Message/Serializer/MessageSerializer.php b/src/Message/Serializer/MessageSerializer.php index cdb8a9d4..21dfb43c 100644 --- a/src/Message/Serializer/MessageSerializer.php +++ b/src/Message/Serializer/MessageSerializer.php @@ -19,6 +19,9 @@ * {@see MessageEncoderInterface}, which encodes it to a string. When unserializing, decodes the string back to an * array and resolves the message class from the type via the resolver, falling back to {@see GenericMessage} * if the type is not registered. + * + * @psalm-import-type MessagePayload from MessageInterface + * @psalm-import-type MessageMeta from MessageInterface */ final class MessageSerializer implements MessageSerializerInterface { @@ -66,9 +69,13 @@ public function unserialize(string $value): MessageInterface if (!is_array($meta)) { throw new MessageSerializerException('Metadata must be an array. Got ' . get_debug_type($meta) . '.'); } + /** @psalm-var MessageMeta $meta */ $class = $this->resolver->resolve($type) ?? GenericMessage::class; - return $class::fromPayload($type, $data['payload'] ?? null)->withMeta($meta); + /** @psalm-var MessagePayload $payload */ + $payload = $data['payload'] ?? null; + + return $class::fromPayload($type, $payload)->withMeta($meta); } } diff --git a/src/Middleware/CallableFactory.php b/src/Middleware/CallableFactory.php index 7b069cca..a79c09c0 100644 --- a/src/Middleware/CallableFactory.php +++ b/src/Middleware/CallableFactory.php @@ -57,6 +57,8 @@ public function create(mixed $definition): callable && array_keys($definition) === [0, 1] && is_string($definition[1]) ) { + /** @psalm-var array{0:mixed,1:string} $definition */ + if (is_object($definition[0])) { $callable = $this->fromObjectDefinition($definition[0], $definition[1]); if ($callable !== null) { @@ -64,7 +66,6 @@ public function create(mixed $definition): callable } } - /** @psalm-suppress PossiblyUndefinedArrayOffset array_keys($definition) === [0, 1] was checked above */ if (is_string($definition[0])) { $callable = $this->fromDefinition($definition[0], $definition[1]); if ($callable !== null) { diff --git a/src/Middleware/Consume/ConsumeMiddlewareDispatcher.php b/src/Middleware/Consume/ConsumeMiddlewareDispatcher.php index c5cdfd03..84ef6a9a 100644 --- a/src/Middleware/Consume/ConsumeMiddlewareDispatcher.php +++ b/src/Middleware/Consume/ConsumeMiddlewareDispatcher.php @@ -85,7 +85,7 @@ public function hasMiddlewares(): bool } /** - * @return Closure[] + * @psalm-return list */ private function buildMiddlewares(): array { diff --git a/src/Middleware/Consume/ConsumeMiddlewareStack.php b/src/Middleware/Consume/ConsumeMiddlewareStack.php index e280da96..2e94cd64 100644 --- a/src/Middleware/Consume/ConsumeMiddlewareStack.php +++ b/src/Middleware/Consume/ConsumeMiddlewareStack.php @@ -20,6 +20,8 @@ final class ConsumeMiddlewareStack implements ConsumeHandlerInterface * @param Closure[] $middlewares Middlewares. * @param ConsumeHandlerInterface $finishHandler Fallback handler * events. + * + * @psalm-param list $middlewares */ public function __construct( private readonly array $middlewares, @@ -28,15 +30,11 @@ public function __construct( public function handleConsume(ConsumeRequest $request): ConsumeRequest { - if ($this->stack === null) { - $this->build(); - } - - /** @psalm-suppress PossiblyNullReference */ + $this->stack ??= $this->build(); return $this->stack->handleConsume($request); } - private function build(): void + private function build(): ConsumeHandlerInterface { $handler = $this->finishHandler; @@ -44,17 +42,22 @@ private function build(): void $handler = $this->wrap($middleware, $handler); } - $this->stack = $handler; + return $handler; } /** * Wrap handler by middlewares. + * + * @psalm-param Closure():ConsumeMiddlewareInterface $middlewareFactory */ private function wrap(Closure $middlewareFactory, ConsumeHandlerInterface $handler): ConsumeHandlerInterface { return new class ($middlewareFactory, $handler) implements ConsumeHandlerInterface { private ?ConsumeMiddlewareInterface $middleware = null; + /** + * @psalm-param Closure():ConsumeMiddlewareInterface $middlewareFactory + */ public function __construct( private readonly Closure $middlewareFactory, private readonly ConsumeHandlerInterface $handler, diff --git a/src/Middleware/FailureHandling/FailureEnvelope.php b/src/Middleware/FailureHandling/FailureEnvelope.php index 4d5064e9..78230435 100644 --- a/src/Middleware/FailureHandling/FailureEnvelope.php +++ b/src/Middleware/FailureHandling/FailureEnvelope.php @@ -12,8 +12,9 @@ use function is_array; /** + * @psalm-type FailureMeta = array> * @extends Envelope>, + * yii-failure: FailureMeta, * ...> * }> */ @@ -21,10 +22,14 @@ final class FailureEnvelope extends Envelope { public const META_FAILURE = 'yii-failure'; + /** + * @psalm-var FailureMeta + */ private array $failureMeta; public function __construct(MessageInterface $message, array $failureMeta = []) { + /** @psalm-var FailureMeta */ $this->failureMeta = $failureMeta === [] ? self::getFailureMetaFromMessage($message) : ArrayHelper::merge( @@ -54,11 +59,15 @@ public static function fromMessage(MessageInterface $message): static ); } + /** + * @psalm-return FailureMeta + */ private static function getFailureMetaFromMessage(MessageInterface $message): array { $meta = $message->getMeta(); if (array_key_exists(self::META_FAILURE, $meta)) { $result = $meta[self::META_FAILURE]; + /** @psalm-var FailureMeta */ return is_array($result) ? $result : []; } return []; diff --git a/src/Middleware/FailureHandling/FailureMiddlewareDispatcher.php b/src/Middleware/FailureHandling/FailureMiddlewareDispatcher.php index 427a7963..ef973f84 100644 --- a/src/Middleware/FailureHandling/FailureMiddlewareDispatcher.php +++ b/src/Middleware/FailureHandling/FailureMiddlewareDispatcher.php @@ -89,7 +89,7 @@ private function init(): void } /** - * @return Closure[] + * @psalm-return list */ private function buildMiddlewares(array|callable|string|FailureMiddlewareInterface ...$definitions): array { diff --git a/src/Middleware/FailureHandling/FailureMiddlewareStack.php b/src/Middleware/FailureHandling/FailureMiddlewareStack.php index 3fa5eb6a..a9d7b21d 100644 --- a/src/Middleware/FailureHandling/FailureMiddlewareStack.php +++ b/src/Middleware/FailureHandling/FailureMiddlewareStack.php @@ -20,6 +20,8 @@ final class FailureMiddlewareStack implements FailureHandlerInterface * @param Closure[] $middlewares Middlewares. * @param FailureHandlerInterface $finishHandler Fallback handler * events. + * + * @psalm-param list $middlewares */ public function __construct( private readonly array $middlewares, @@ -28,15 +30,11 @@ public function __construct( public function handleFailure(FailureHandlingRequest $request): FailureHandlingRequest { - if ($this->stack === null) { - $this->build(); - } - - /** @psalm-suppress PossiblyNullReference */ + $this->stack ??= $this->build(); return $this->stack->handleFailure($request); } - private function build(): void + private function build(): FailureHandlerInterface { $handler = $this->finishHandler; @@ -44,17 +42,21 @@ private function build(): void $handler = $this->wrap($middleware, $handler); } - $this->stack = $handler; + return $handler; } /** * Wrap handler by middlewares. + * @psalm-param Closure():FailureMiddlewareInterface $middlewareFactory */ private function wrap(Closure $middlewareFactory, FailureHandlerInterface $handler): FailureHandlerInterface { return new class ($middlewareFactory, $handler) implements FailureHandlerInterface { private ?FailureMiddlewareInterface $middleware = null; + /** + * @psalm-param Closure():FailureMiddlewareInterface $middlewareFactory + */ public function __construct( private readonly Closure $middlewareFactory, private readonly FailureHandlerInterface $handler, diff --git a/src/Middleware/InvalidMiddlewareDefinitionException.php b/src/Middleware/InvalidMiddlewareDefinitionException.php index 704fba3e..66061e27 100644 --- a/src/Middleware/InvalidMiddlewareDefinitionException.php +++ b/src/Middleware/InvalidMiddlewareDefinitionException.php @@ -13,10 +13,7 @@ final class InvalidMiddlewareDefinitionException extends InvalidArgumentException { - /** - * @param array|callable|string $middlewareDefinition - */ - public function __construct($middlewareDefinition, int $code = 0, ?Throwable $previous = null) + public function __construct(mixed $middlewareDefinition, int $code = 0, ?Throwable $previous = null) { $message = 'Parameter should be either middleware class name or a callable.'; diff --git a/src/Middleware/MiddlewareFactory.php b/src/Middleware/MiddlewareFactory.php index 5204a04a..b0289284 100644 --- a/src/Middleware/MiddlewareFactory.php +++ b/src/Middleware/MiddlewareFactory.php @@ -14,6 +14,9 @@ /** * @template T of object + * + * @psalm-import-type ArrayDefinitionConfig from ArrayDefinition + * * @internal */ abstract class MiddlewareFactory @@ -83,8 +86,9 @@ private function getFromContainer(string $definition): object } /** - * @return T|null * @throws InvalidMiddlewareDefinitionException + * + * @psalm-return T|null */ private function tryGetFromArrayDefinition(array $definition): ?object { @@ -92,17 +96,18 @@ private function tryGetFromArrayDefinition(array $definition): ?object try { DefinitionValidator::validateArrayDefinition($definition); + /** @psalm-var ArrayDefinitionConfig $definition */ $middleware = ArrayDefinition::fromConfig($definition)->resolve($this->container); if (is_subclass_of($middleware, $interface)) { - /** @var T */ + /** @psalm-var T */ return $middleware; } - throw new InvalidMiddlewareDefinitionException($definition); + return null; } catch (InvalidConfigException) { } - throw new InvalidMiddlewareDefinitionException($definition); + return null; } } diff --git a/src/Middleware/Push/PushMiddlewareDispatcher.php b/src/Middleware/Push/PushMiddlewareDispatcher.php index 8fc3af28..53c1b01e 100644 --- a/src/Middleware/Push/PushMiddlewareDispatcher.php +++ b/src/Middleware/Push/PushMiddlewareDispatcher.php @@ -97,7 +97,7 @@ public function hasMiddlewares(): bool } /** - * @return Closure[] + * @psalm-return list */ private function buildMiddlewares(): array { diff --git a/src/Middleware/Push/PushMiddlewareStack.php b/src/Middleware/Push/PushMiddlewareStack.php index 9d6ba251..de249b03 100644 --- a/src/Middleware/Push/PushMiddlewareStack.php +++ b/src/Middleware/Push/PushMiddlewareStack.php @@ -23,6 +23,8 @@ final class PushMiddlewareStack implements PushHandlerInterface /** * @param Closure[] $middlewares Middlewares. * @param PushHandlerInterface $finishHandler Final handler invoked after all middlewares are processed. + * + * @psalm-param list $middlewares */ public function __construct( private readonly array $middlewares, @@ -31,15 +33,11 @@ public function __construct( public function handlePush(MessageInterface $message): MessageInterface { - if ($this->stack === null) { - $this->build(); - } - - /** @psalm-suppress PossiblyNullReference */ + $this->stack ??= $this->build(); return $this->stack->handlePush($message); } - private function build(): void + private function build(): PushHandlerInterface { $handler = $this->finishHandler; @@ -47,17 +45,22 @@ private function build(): void $handler = $this->wrap($middleware, $handler); } - $this->stack = $handler; + return $handler; } /** * Wrap handler by middlewares. + * + * @psalm-param Closure():PushMiddlewareInterface $middlewareFactory */ private function wrap(Closure $middlewareFactory, PushHandlerInterface $handler): PushHandlerInterface { return new class ($middlewareFactory, $handler) implements PushHandlerInterface { private ?PushMiddlewareInterface $middleware = null; + /** + * @psalm-param Closure():PushMiddlewareInterface $middlewareFactory + */ public function __construct( private readonly Closure $middlewareFactory, private readonly PushHandlerInterface $handler, diff --git a/src/Provider/QueueProviderInterface.php b/src/Provider/QueueProviderInterface.php index e3c25929..6df9cb15 100644 --- a/src/Provider/QueueProviderInterface.php +++ b/src/Provider/QueueProviderInterface.php @@ -12,8 +12,10 @@ */ interface QueueProviderInterface { - /** @psalm-suppress MissingClassConstType */ - public const DEFAULT_QUEUE = 'yii-queue'; + /** + * @psalm-suppress MissingClassConstType + */ + final public const DEFAULT_QUEUE = 'yii-queue'; /** * Finds a queue by name and returns it. diff --git a/tests/Unit/Support/TestMessage.php b/tests/Unit/Support/TestMessage.php index 4bcdc3a6..31b1ab76 100644 --- a/tests/Unit/Support/TestMessage.php +++ b/tests/Unit/Support/TestMessage.php @@ -5,11 +5,10 @@ namespace Yiisoft\Queue\Tests\Unit\Support; use Yiisoft\Queue\Message\Message; -use Yiisoft\Queue\Message\MessageInterface; final class TestMessage extends Message { - public static function fromPayload(string $type, mixed $payload): MessageInterface + public static function fromPayload(string $type, mixed $payload): static { return new self(); }