From ab628c95cbc76ee95f3509d329be6c586de5db76 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 14 Aug 2026 11:56:57 +0300 Subject: [PATCH 1/5] Introduce handler resolver --- config/di.php | 7 +- config/params.php | 4 +- src/Message/Handler/CallableHandler.php | 30 +++ src/Message/Handler/HandlerInterface.php | 18 ++ .../Resolver/HandlerNotFoundException.php | 25 +++ .../Handler/Resolver/HandlerResolver.php | 61 ++++++ .../Resolver/HandlerResolverInterface.php | 22 +++ src/Message/MessageHandlerInterface.php | 10 - src/Worker/Worker.php | 72 +------ tests/Benchmark/QueueBench.php | 15 +- tests/Integration/MessageConsumingTest.php | 22 +-- tests/Integration/MiddlewareTest.php | 7 +- tests/Integration/Support/TestHandler.php | 4 +- tests/TestCase.php | 11 +- .../Handler/Resolver/HandlerResolverTest.php | 167 ++++++++++++++++ tests/Unit/WorkerTest.php | 187 +++--------------- 16 files changed, 393 insertions(+), 269 deletions(-) create mode 100644 src/Message/Handler/CallableHandler.php create mode 100644 src/Message/Handler/HandlerInterface.php create mode 100644 src/Message/Handler/Resolver/HandlerNotFoundException.php create mode 100644 src/Message/Handler/Resolver/HandlerResolver.php create mode 100644 src/Message/Handler/Resolver/HandlerResolverInterface.php delete mode 100644 src/Message/MessageHandlerInterface.php create mode 100644 tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php diff --git a/config/di.php b/config/di.php index 86224d7c..1c8f3b75 100644 --- a/config/di.php +++ b/config/di.php @@ -21,16 +21,19 @@ use Yiisoft\Queue\Middleware\Push\PushMiddlewareConfig; use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactory; use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactoryInterface; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolverInterface; use Yiisoft\Queue\Worker\Worker as QueueWorker; use Yiisoft\Queue\Worker\WorkerInterface; /* @var array $params */ return [ - QueueWorker::class => [ - 'class' => QueueWorker::class, + HandlerResolver::class => [ + 'class' => HandlerResolver::class, '__construct()' => [$params['yiisoft/queue']['handlers']], ], + HandlerResolverInterface::class => HandlerResolver::class, WorkerInterface::class => QueueWorker::class, LoopInterface::class => static function (ContainerInterface $container): LoopInterface { return \extension_loaded('pcntl') diff --git a/config/params.php b/config/params.php index 03956a11..e502d23a 100644 --- a/config/params.php +++ b/config/params.php @@ -9,7 +9,7 @@ use Yiisoft\Queue\Debug\QueueConsumerProviderProxy; use Yiisoft\Queue\Debug\QueueProducerProviderProxy; use Yiisoft\Queue\Debug\QueueWorkerInterfaceProxy; -use Yiisoft\Queue\Message\MessageHandlerInterface; +use Yiisoft\Queue\Message\Handler\HandlerInterface; use Yiisoft\Queue\Message\Serializer\MessageSerializer; use Yiisoft\Queue\Provider\QueueConsumerProviderInterface; use Yiisoft\Queue\Provider\QueueProducerProviderInterface; @@ -35,7 +35,7 @@ 'messages' => [], /** * Map of message type to handler. The worker uses this to find the handler for a received message. - * A handler may be a class name implementing {@see MessageHandlerInterface}, a callable, or any definition + * A handler may be a class name implementing {@see HandlerInterface}, a callable, or any definition * supported by yiisoft/injector. Example: * [ * 'send-email' => SendEmailHandler::class, diff --git a/src/Message/Handler/CallableHandler.php b/src/Message/Handler/CallableHandler.php new file mode 100644 index 00000000..25a22e91 --- /dev/null +++ b/src/Message/Handler/CallableHandler.php @@ -0,0 +1,30 @@ +handler)($message); + } +} diff --git a/src/Message/Handler/HandlerInterface.php b/src/Message/Handler/HandlerInterface.php new file mode 100644 index 00000000..79278411 --- /dev/null +++ b/src/Message/Handler/HandlerInterface.php @@ -0,0 +1,18 @@ + Cache of resolved handlers */ + private array $cache = []; + + public function __construct( + /** @var array */ + private readonly array $handlers, + private readonly ContainerInterface $container, + private readonly CallableFactory $callableFactory, + ) {} + + public function resolve(string $messageType): HandlerInterface + { + if ($messageType === '') { + throw new HandlerNotFoundException($messageType); + } + + if (array_key_exists($messageType, $this->cache)) { + return $this->cache[$messageType]; + } + + $definition = $this->handlers[$messageType] ?? $messageType; + + if (is_string($definition) && $this->container->has($definition)) { + $resolved = $this->container->get($definition); + + if ($resolved instanceof HandlerInterface) { + return $this->cache[$messageType] = $resolved; + } + } + + try { + /** @psalm-var callable(MessageInterface): void $callable */ + $callable = $this->callableFactory->create($definition); + + return $this->cache[$messageType] = new CallableHandler($callable); + } catch (InvalidCallableConfigurationException $exception) { + throw new HandlerNotFoundException($messageType, 0, $exception); + } + } +} diff --git a/src/Message/Handler/Resolver/HandlerResolverInterface.php b/src/Message/Handler/Resolver/HandlerResolverInterface.php new file mode 100644 index 00000000..ed9bdf85 --- /dev/null +++ b/src/Message/Handler/Resolver/HandlerResolverInterface.php @@ -0,0 +1,22 @@ + Cache of resolved handlers */ - private array $handlersCached = []; - public function __construct( - /** @var array */ - private readonly array $handlers, private readonly LoggerInterface $logger, - private readonly Injector $injector, - private readonly ContainerInterface $container, private readonly ConsumeMiddlewareDispatcher $consumeMiddlewareDispatcher, private readonly FailureMiddlewareDispatcher $failureMiddlewareDispatcher, - private readonly CallableFactory $callableFactory, + private readonly HandlerResolverInterface $handlerResolver, ) {} /** @@ -61,26 +45,17 @@ public function process( $this->logger->info('Processing message #{message}.', ['message' => $messageId]); } - $messageType = $message->getType(); - try { - $handler = $this->getHandler($messageType); - } catch (InvalidCallableConfigurationException $exception) { - throw new RuntimeException(sprintf('Queue handler for message type "%s" does not exist.', $messageType), 0, $exception); - } - - if ($handler === null) { - throw new RuntimeException(sprintf('Queue handler for message type "%s" does not exist.', $messageType)); - } + $handler = $this->handlerResolver->resolve($message->getType()); $request = new ConsumeRequest($message, $queueName); - $closure = fn(MessageInterface $message): mixed => $this->injector->invoke($handler, [$message]); + $finishHandler = new ConsumeFinalHandler($handler->handle(...)); try { - return $this->consumeMiddlewareDispatcher->dispatch($request, $this->createConsumeHandler($closure))->getMessage(); + return $this->consumeMiddlewareDispatcher->dispatch($request, $finishHandler)->getMessage(); } catch (Throwable $exception) { $request = new FailureHandlingRequest($request->getMessage(), $exception, $request->getQueueName(), $retryProducer); try { - $result = $this->failureMiddlewareDispatcher->dispatch($request, $this->createFailureHandler()); + $result = $this->failureMiddlewareDispatcher->dispatch($request, new FailureFinalHandler()); $this->logger->info($exception->getMessage()); return $result->getMessage(); @@ -91,39 +66,4 @@ public function process( } } } - - private function getHandler(string $messageType): ?callable - { - if ($messageType === '') { - return null; - } - - if (!array_key_exists($messageType, $this->handlersCached)) { - $definition = $this->handlers[$messageType] ?? $messageType; - - if (is_string($definition) && $this->container->has($definition)) { - $resolved = $this->container->get($definition); - - if ($resolved instanceof MessageHandlerInterface) { - $this->handlersCached[$messageType] = $resolved->handle(...); - - return $this->handlersCached[$messageType]; - } - } - - $this->handlersCached[$messageType] = $this->callableFactory->create($definition); - } - - return $this->handlersCached[$messageType]; - } - - private function createConsumeHandler(Closure $handler): ConsumeHandlerInterface - { - return new ConsumeFinalHandler($handler); - } - - private function createFailureHandler(): FailureHandlerInterface - { - return new FailureFinalHandler(); - } } diff --git a/tests/Benchmark/QueueBench.php b/tests/Benchmark/QueueBench.php index 7f56b83b..51485bc5 100644 --- a/tests/Benchmark/QueueBench.php +++ b/tests/Benchmark/QueueBench.php @@ -7,7 +7,6 @@ use Generator; use PhpBench\Attributes\ParamProviders; use Psr\Log\NullLogger; -use Yiisoft\Injector\Injector; use Yiisoft\Queue\Cli\SimpleLoop; use Yiisoft\Queue\Message\IdEnvelope; use Yiisoft\Queue\Message\GenericMessage; @@ -25,6 +24,7 @@ use Yiisoft\Queue\QueueConsumer; use Yiisoft\Queue\QueueConsumerInterface; use Yiisoft\Queue\QueueProducerInterface; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; use Yiisoft\Queue\Tests\Benchmark\Support\VoidAdapter; use Yiisoft\Queue\Worker\Worker; use Yiisoft\Test\Support\Container\SimpleContainer; @@ -43,18 +43,19 @@ public function __construct() $logger = new NullLogger(); $worker = new Worker( - [ - 'foo' => static function (): void {}, - ], $logger, - new Injector($container), - $container, new ConsumeMiddlewareDispatcher(new ConsumeMiddlewareFactory($container, $callableFactory)), new FailureMiddlewareDispatcher( new FailureMiddlewareFactory($container, $callableFactory), [], ), - $callableFactory, + new HandlerResolver( + [ + 'foo' => static function (): void {}, + ], + $container, + $callableFactory, + ), ); $this->serializer = new MessageSerializer(new JsonMessageEncoder()); $this->adapter = new VoidAdapter($this->serializer); diff --git a/tests/Integration/MessageConsumingTest.php b/tests/Integration/MessageConsumingTest.php index f928f6f4..66146726 100644 --- a/tests/Integration/MessageConsumingTest.php +++ b/tests/Integration/MessageConsumingTest.php @@ -6,7 +6,6 @@ use Psr\Container\ContainerInterface; use Psr\Log\NullLogger; -use Yiisoft\Injector\Injector; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; use Yiisoft\Queue\Middleware\CallableFactory; @@ -14,6 +13,7 @@ use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareFactoryInterface; use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareDispatcher; use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareFactoryInterface; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; use Yiisoft\Queue\Tests\Integration\Support\TestHandler; use Yiisoft\Queue\Tests\TestCase; use Yiisoft\Queue\Worker\Worker; @@ -31,16 +31,17 @@ public function testMessagesConsumed(): void $container = $this->createMock(ContainerInterface::class); $callableFactory = new CallableFactory($container); $worker = new Worker( - [ - 'test' => fn(MessageInterface $message): mixed => $this->messagesProcessed[] = $message->getPayload(), - 'test2' => fn(MessageInterface $message): mixed => $this->messagesProcessedSecond[] = $message->getPayload(), - ], new NullLogger(), - new Injector($container), - $container, new ConsumeMiddlewareDispatcher($this->createMock(ConsumeMiddlewareFactoryInterface::class)), new FailureMiddlewareDispatcher($this->createMock(FailureMiddlewareFactoryInterface::class), []), - $callableFactory, + new HandlerResolver( + [ + 'test' => fn(MessageInterface $message): mixed => $this->messagesProcessed[] = $message->getPayload(), + 'test2' => fn(MessageInterface $message): mixed => $this->messagesProcessedSecond[] = $message->getPayload(), + ], + $container, + $callableFactory, + ), ); $messages = [1, 'foo', 'bar-baz']; @@ -61,13 +62,10 @@ public function testMessagesConsumedByHandlerClass(): void $container->method('has')->with(TestHandler::class)->willReturn(true); $callableFactory = new CallableFactory($container); $worker = new Worker( - [], new NullLogger(), - new Injector($container), - $container, new ConsumeMiddlewareDispatcher($this->createMock(ConsumeMiddlewareFactoryInterface::class)), new FailureMiddlewareDispatcher($this->createMock(FailureMiddlewareFactoryInterface::class), []), - $callableFactory, + new HandlerResolver([], $container, $callableFactory), ); $messages = [1, 'foo', 'bar-baz']; diff --git a/tests/Integration/MiddlewareTest.php b/tests/Integration/MiddlewareTest.php index 9118d0e5..bf1eadd6 100644 --- a/tests/Integration/MiddlewareTest.php +++ b/tests/Integration/MiddlewareTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use Yiisoft\Injector\Injector; use Yiisoft\Test\Support\Container\SimpleContainer; use Yiisoft\Test\Support\Log\SimpleLogger; use Yiisoft\Queue\Message\GenericMessage; @@ -26,6 +25,7 @@ use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactory; use Yiisoft\Queue\SyncQueueProducer; use Yiisoft\Queue\QueueProducerInterface; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; use Yiisoft\Queue\Tests\Integration\Support\TestMiddleware; use Yiisoft\Queue\Worker\Worker; use Yiisoft\Queue\Worker\WorkerInterface; @@ -104,13 +104,10 @@ public function testFullStackConsume(): void ); $worker = new Worker( - ['test' => static fn() => true], new SimpleLogger(), - new Injector($container), - $container, $consumeMiddlewareDispatcher, $failureMiddlewareDispatcher, - $callableFactory, + new HandlerResolver(['test' => static fn() => true], $container, $callableFactory), ); $message = new GenericMessage('test', ['initial']); diff --git a/tests/Integration/Support/TestHandler.php b/tests/Integration/Support/TestHandler.php index 34cd004d..422e3480 100644 --- a/tests/Integration/Support/TestHandler.php +++ b/tests/Integration/Support/TestHandler.php @@ -4,10 +4,10 @@ namespace Yiisoft\Queue\Tests\Integration\Support; -use Yiisoft\Queue\Message\MessageHandlerInterface; +use Yiisoft\Queue\Message\Handler\HandlerInterface; use Yiisoft\Queue\Message\MessageInterface; -final class TestHandler implements MessageHandlerInterface +final class TestHandler implements HandlerInterface { public function __construct(public array $messagesProcessed = []) {} diff --git a/tests/TestCase.php b/tests/TestCase.php index 5ed6f752..cb81a762 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,6 @@ use Psr\Container\ContainerInterface; use Psr\Log\NullLogger; use RuntimeException; -use Yiisoft\Injector\Injector; use Yiisoft\Test\Support\Container\SimpleContainer; use Yiisoft\Queue\Adapter\AdapterInterface; use Yiisoft\Queue\Cli\LoopInterface; @@ -24,6 +23,7 @@ use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactory; use Yiisoft\Queue\AsyncQueueProducer; use Yiisoft\Queue\QueueProducerInterface; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; use Yiisoft\Queue\SyncQueueProducer; use Yiisoft\Queue\Worker\Worker; use Yiisoft\Queue\Worker\WorkerInterface; @@ -118,13 +118,14 @@ protected function createLoop(): LoopInterface protected function createWorker(): WorkerInterface { return new Worker( - $this->getMessageHandlers(), new NullLogger(), - new Injector($this->getContainer()), - $this->getContainer(), $this->getConsumeMiddlewareDispatcher(), $this->getFailureMiddlewareDispatcher(), - new CallableFactory($this->getContainer()), + new HandlerResolver( + $this->getMessageHandlers(), + $this->getContainer(), + new CallableFactory($this->getContainer()), + ), ); } diff --git a/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php b/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php new file mode 100644 index 00000000..94bd8911 --- /dev/null +++ b/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php @@ -0,0 +1,167 @@ + $handler], $container, new CallableFactory($container)); + + $resolvedHandler = $resolver->resolve($message->getType()); + $resolvedHandler->handle($message); + + $processedMessages = FakeHandler::$processedMessages; + FakeHandler::$processedMessages = []; + + $this->assertSame([$message], $processedMessages); + } + + public static function handlerDefinitionDataProvider(): iterable + { + yield 'definition' => [ + FakeHandler::class, + [FakeHandler::class => new FakeHandler()], + ]; + yield 'definition-object' => [ + [new FakeHandler(), 'handle'], + [], + ]; + yield 'definition-class' => [ + [FakeHandler::class, 'handle'], + [FakeHandler::class => new FakeHandler()], + ]; + yield 'definition-not-found-class-but-exist-in-container' => [ + ['not-found-class-name', 'handle'], + ['not-found-class-name' => new FakeHandler()], + ]; + yield 'callable' => [ + function (MessageInterface $message) { + FakeHandler::$processedMessages[] = $message; + }, + [], + ]; + } + + public function testResolveCachesResolvedHandler(): void + { + $container = new SimpleContainer([FakeHandler::class => new FakeHandler()]); + $resolver = new HandlerResolver(['simple' => FakeHandler::class], $container, new CallableFactory($container)); + + $this->assertSame($resolver->resolve('simple'), $resolver->resolve('simple')); + } + + public function testResolveStaticMethodHandler(): void + { + $container = new SimpleContainer(); + $resolver = new HandlerResolver( + ['static-handler' => StaticMessageHandler::handle(...)], + $container, + new CallableFactory($container), + ); + + StaticMessageHandler::$wasHandled = false; + $resolvedHandler = $resolver->resolve('static-handler'); + $resolvedHandler->handle(new GenericMessage('static-handler', null)); + + $this->assertTrue(StaticMessageHandler::$wasHandled); + } + + public function testResolveThrowsWhenDefinitionMethodUndefined(): void + { + $this->expectException(HandlerNotFoundException::class); + $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); + + $container = new SimpleContainer([FakeHandler::class => new FakeHandler()]); + $resolver = new HandlerResolver( + ['simple' => [FakeHandler::class, 'undefinedMethod']], + $container, + new CallableFactory($container), + ); + + $resolver->resolve('simple'); + } + + public function testResolveThrowsWhenDefinitionClassUndefined(): void + { + $this->expectException(HandlerNotFoundException::class); + $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); + + $container = new SimpleContainer([FakeHandler::class => new FakeHandler()]); + $resolver = new HandlerResolver( + ['simple' => ['UndefinedClass', 'handle']], + $container, + new CallableFactory($container), + ); + + $resolver->resolve('simple'); + } + + public function testResolveThrowsWhenDefinitionClassNotFoundInContainer(): void + { + $this->expectException(HandlerNotFoundException::class); + $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); + + $container = new SimpleContainer(); + $resolver = new HandlerResolver( + ['simple' => [FakeHandler::class, 'handle']], + $container, + new CallableFactory($container), + ); + + $resolver->resolve('simple'); + } + + public function testResolveThrowsWhenHandlerNotFoundInContainer(): void + { + $this->expectException(HandlerNotFoundException::class); + $this->expectExceptionMessage('Queue handler for message type "nonexistent" does not exist'); + + $container = new SimpleContainer(); + $resolver = new HandlerResolver([], $container, new CallableFactory($container)); + + $resolver->resolve('nonexistent'); + } + + public function testResolveThrowsWhenHandlerInContainerNotImplementingInterface(): void + { + $this->expectException(HandlerNotFoundException::class); + $this->expectExceptionMessage('Queue handler for message type "invalid" does not exist'); + + $container = new SimpleContainer([ + 'invalid' => new class { + public function handle(): void {} + }, + ]); + $resolver = new HandlerResolver([], $container, new CallableFactory($container)); + + $resolver->resolve('invalid'); + } + + public function testResolveThrowsWhenMessageTypeIsEmpty(): void + { + $this->expectException(HandlerNotFoundException::class); + $this->expectExceptionMessage('Queue handler for message type "" does not exist'); + + $container = new SimpleContainer(); + $resolver = new HandlerResolver([], $container, new CallableFactory($container)); + + $resolver->resolve(''); + } +} diff --git a/tests/Unit/WorkerTest.php b/tests/Unit/WorkerTest.php index 6840f828..1ef46e84 100644 --- a/tests/Unit/WorkerTest.php +++ b/tests/Unit/WorkerTest.php @@ -4,45 +4,39 @@ namespace Yiisoft\Queue\Tests\Unit; -use PHPUnit\Framework\Attributes\DataProvider; -use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use RuntimeException; -use Yiisoft\Injector\Injector; -use Yiisoft\Test\Support\Container\SimpleContainer; use Yiisoft\Test\Support\Log\SimpleLogger; use Yiisoft\Queue\Exception\MessageFailureException; +use Yiisoft\Queue\Message\Handler\CallableHandler; +use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolverInterface; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareFactoryInterface; -use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareInterface; use Yiisoft\Queue\Middleware\FailureHandling\FailureHandlingRequest; -use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareInterface; +use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareDispatcher; use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareFactoryInterface; -use Yiisoft\Queue\Middleware\CallableFactory; +use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareInterface; use Yiisoft\Queue\Tests\App\FakeHandler; -use Yiisoft\Queue\Tests\App\StaticMessageHandler; use Yiisoft\Queue\Tests\TestCase; use Yiisoft\Queue\Worker\Worker; use PHPUnit\Framework\MockObject\MockObject; final class WorkerTest extends TestCase { - #[DataProvider('messageHandledDataProvider')] - public function testMessageHandled(mixed $handler, array $containerServices): void + public function testMessageHandled(): void { $message = new GenericMessage('simple', ['test-data']); $logger = new SimpleLogger(); - $container = new SimpleContainer($containerServices); - $handlers = ['simple' => $handler]; + $handlerResolver = $this->createHandlerResolver($message, static function (MessageInterface $message): void { + FakeHandler::$processedMessages[] = $message; + }); - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container, $logger); - - $worker->process($message, $queueName); + $worker = $this->createWorkerByParams($handlerResolver, $logger); + $worker->process($message, 'test-queue'); $processedMessages = FakeHandler::$processedMessages; FakeHandler::$processedMessages = []; @@ -54,93 +48,19 @@ public function testMessageHandled(mixed $handler, array $containerServices): vo $this->assertStringContainsString('Processing message without ID.', $messages[0]['message']); } - public static function messageHandledDataProvider(): iterable - { - yield 'definition' => [ - FakeHandler::class, - [FakeHandler::class => new FakeHandler()], - ]; - yield 'definition-object' => [ - [new FakeHandler(), 'handle'], - [], - ]; - yield 'definition-class' => [ - [FakeHandler::class, 'handle'], - [FakeHandler::class => new FakeHandler()], - ]; - yield 'definition-not-found-class-but-exist-in-container' => [ - ['not-found-class-name', 'handle'], - ['not-found-class-name' => new FakeHandler()], - ]; - yield 'static-definition' => [ - FakeHandler::staticHandle(...), - [FakeHandler::class => new FakeHandler()], - ]; - yield 'callable' => [ - function (MessageInterface $message) { - FakeHandler::$processedMessages[] = $message; - }, - [], - ]; - } - - public function testMessageFailWithDefinitionUndefinedMethodHandler(): void - { - $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); - - $message = new GenericMessage('simple', ['test-data']); - $handler = new FakeHandler(); - $container = new SimpleContainer([FakeHandler::class => $handler]); - $handlers = ['simple' => [FakeHandler::class, 'undefinedMethod']]; - - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container); - - $worker->process($message, $queueName); - } - - public function testMessageFailWithDefinitionUndefinedClassHandler(): void - { - $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); - - $message = new GenericMessage('simple', ['test-data']); - $logger = new SimpleLogger(); - $handler = new FakeHandler(); - $container = new SimpleContainer([FakeHandler::class => $handler]); - $handlers = ['simple' => ['UndefinedClass', 'handle']]; - - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container, $logger); - - $worker->process($message, $queueName); - } - - public function testMessageFailWithDefinitionClassNotFoundInContainerHandler(): void - { - $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); - $message = new GenericMessage('simple', ['test-data']); - $container = new SimpleContainer(); - $handlers = ['simple' => [FakeHandler::class, 'handle']]; - - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container); - - $worker->process($message, $queueName); - } - public function testMessageFailWithDefinitionHandlerException(): void { $message = new GenericMessage('simple', ['test-data']); $logger = new SimpleLogger(); - $handler = new FakeHandler(); - $container = new SimpleContainer([FakeHandler::class => $handler]); - $handlers = ['simple' => [FakeHandler::class, 'handleWithException']]; + $handlerResolver = $this->createHandlerResolver($message, static function (): never { + throw new RuntimeException('Test exception'); + }); - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container, $logger); + $worker = $this->createWorkerByParams($handlerResolver, $logger); try { - $worker->process($message, $queueName); + $worker->process($message, 'test-queue'); + self::fail('Exception was not thrown.'); } catch (MessageFailureException $exception) { self::assertSame($exception::class, MessageFailureException::class); self::assertSame($exception->getMessage(), "Processing of message without ID is stopped because of an exception:\nTest exception."); @@ -155,38 +75,6 @@ public function testMessageFailWithDefinitionHandlerException(): void } } - public function testHandlerNotFoundInContainer(): void - { - $message = new GenericMessage('nonexistent', ['test-data']); - $container = new SimpleContainer(); - $handlers = []; - - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Queue handler for message type "nonexistent" does not exist'); - $worker->process($message, $queueName); - } - - public function testHandlerInContainerNotImplementingInterface(): void - { - $message = new GenericMessage('invalid', ['test-data']); - $container = new SimpleContainer([ - 'invalid' => new class { - public function handle(): void {} - }, - ]); - $handlers = []; - - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Queue handler for message type "invalid" does not exist'); - $worker->process($message, $queueName); - } - public function testMessageFailureIsHandledSuccessfully(): void { $message = new GenericMessage('simple', null); @@ -212,42 +100,28 @@ public function testMessageFailureIsHandledSuccessfully(): void $failureMiddlewareFactory->method('createFailureMiddleware')->willReturn($failureMiddleware); $failureDispatcher = new FailureMiddlewareDispatcher($failureMiddlewareFactory, ['test-queue' => ['simple']]); - $container = new SimpleContainer(); - $worker = new Worker( - ['simple' => fn() => null], - new NullLogger(), - new Injector($container), - $container, - $consumeDispatcher, - $failureDispatcher, - new CallableFactory($container), - ); + $handlerResolver = $this->createHandlerResolver($message, static fn() => null); + $worker = $this->createWorkerByParams($handlerResolver, new NullLogger(), $consumeDispatcher, $failureDispatcher); $result = $worker->process($message, $queueName); self::assertSame($finalMessage, $result); } - public function testStaticMethodHandler(): void + private function createHandlerResolver(MessageInterface $message, callable $handler): HandlerResolverInterface { - $message = new GenericMessage('static-handler', ['test-data']); - $container = new SimpleContainer(); - $handlers = [ - 'static-handler' => StaticMessageHandler::handle(...), - ]; - - $queueName = 'test-queue'; - $worker = $this->createWorkerByParams($handlers, $container); + /** @var HandlerResolverInterface&MockObject $handlerResolver */ + $handlerResolver = $this->createMock(HandlerResolverInterface::class); + $handlerResolver->method('resolve')->with($message->getType())->willReturn(new CallableHandler($handler)); - StaticMessageHandler::$wasHandled = false; - $worker->process($message, $queueName); - $this->assertTrue(StaticMessageHandler::$wasHandled); + return $handlerResolver; } private function createWorkerByParams( - array $handlers, - ContainerInterface $container, + HandlerResolverInterface $handlerResolver, ?LoggerInterface $logger = null, + ?ConsumeMiddlewareDispatcher $consumeMiddlewareDispatcher = null, + ?FailureMiddlewareDispatcher $failureMiddlewareDispatcher = null, ): Worker { /** @var ConsumeMiddlewareFactoryInterface&MockObject $consumeMiddlewareFactory */ $consumeMiddlewareFactory = $this->createMock(ConsumeMiddlewareFactoryInterface::class); @@ -255,13 +129,10 @@ private function createWorkerByParams( $failureMiddlewareFactory = $this->createMock(FailureMiddlewareFactoryInterface::class); return new Worker( - $handlers, $logger ?? new NullLogger(), - new Injector($container), - $container, - new ConsumeMiddlewareDispatcher($consumeMiddlewareFactory), - new FailureMiddlewareDispatcher($failureMiddlewareFactory, []), - new CallableFactory($container), + $consumeMiddlewareDispatcher ?? new ConsumeMiddlewareDispatcher($consumeMiddlewareFactory), + $failureMiddlewareDispatcher ?? new FailureMiddlewareDispatcher($failureMiddlewareFactory, []), + $handlerResolver, ); } } From 596c7a66b3b355543a6002eaee1436d2951aee96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Aug 2026 08:58:02 +0000 Subject: [PATCH 2/5] Apply PHP CS Fixer and Rector changes (CI) --- src/Worker/Worker.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Worker/Worker.php b/src/Worker/Worker.php index ebe8b951..94d2b4d4 100644 --- a/src/Worker/Worker.php +++ b/src/Worker/Worker.php @@ -4,7 +4,6 @@ namespace Yiisoft\Queue\Worker; -use Closure; use Psr\Log\LoggerInterface; use Throwable; use Yiisoft\Queue\Exception\MessageFailureException; @@ -13,11 +12,9 @@ use Yiisoft\Queue\Middleware\Consume\ConsumeFinalHandler; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\ConsumeRequest; -use Yiisoft\Queue\Middleware\Consume\ConsumeHandlerInterface; use Yiisoft\Queue\Middleware\FailureHandling\FailureFinalHandler; use Yiisoft\Queue\Middleware\FailureHandling\FailureHandlingRequest; use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareDispatcher; -use Yiisoft\Queue\Middleware\FailureHandling\FailureHandlerInterface; use Yiisoft\Queue\QueueProducerInterface; use Yiisoft\Queue\Message\IdEnvelope; From 170acdcd35e51ddb07035facfa98cdb68858634b Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 18 Aug 2026 10:09:59 +0300 Subject: [PATCH 3/5] Improve --- config/di.php | 5 +- src/Message/Handler/CallableHandler.php | 5 +- .../HandlerNotFoundException.php | 2 +- src/Message/Handler/HandlerResolver.php | 144 ++++++++++++++++++ .../InvalidHandlerConfigurationException.php | 27 ++++ .../Handler/Resolver/HandlerResolver.php | 61 -------- .../Resolver/HandlerResolverInterface.php | 22 --- src/Middleware/CallableFactory.php | 5 +- src/Worker/Worker.php | 4 +- tests/Benchmark/QueueBench.php | 2 +- tests/Integration/MessageConsumingTest.php | 7 +- tests/Integration/MiddlewareTest.php | 4 +- tests/TestCase.php | 3 +- .../Handler/Resolver/HandlerResolverTest.php | 40 +++-- tests/Unit/WorkerTest.php | 18 ++- 15 files changed, 213 insertions(+), 136 deletions(-) rename src/Message/Handler/{Resolver => }/HandlerNotFoundException.php (91%) create mode 100644 src/Message/Handler/HandlerResolver.php create mode 100644 src/Message/Handler/InvalidHandlerConfigurationException.php delete mode 100644 src/Message/Handler/Resolver/HandlerResolver.php delete mode 100644 src/Message/Handler/Resolver/HandlerResolverInterface.php diff --git a/config/di.php b/config/di.php index 1c8f3b75..e8ae8707 100644 --- a/config/di.php +++ b/config/di.php @@ -21,8 +21,7 @@ use Yiisoft\Queue\Middleware\Push\PushMiddlewareConfig; use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactory; use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactoryInterface; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolverInterface; +use Yiisoft\Queue\Message\Handler\HandlerResolver; use Yiisoft\Queue\Worker\Worker as QueueWorker; use Yiisoft\Queue\Worker\WorkerInterface; @@ -30,10 +29,8 @@ return [ HandlerResolver::class => [ - 'class' => HandlerResolver::class, '__construct()' => [$params['yiisoft/queue']['handlers']], ], - HandlerResolverInterface::class => HandlerResolver::class, WorkerInterface::class => QueueWorker::class, LoopInterface::class => static function (ContainerInterface $container): LoopInterface { return \extension_loaded('pcntl') diff --git a/src/Message/Handler/CallableHandler.php b/src/Message/Handler/CallableHandler.php index 25a22e91..35cab228 100644 --- a/src/Message/Handler/CallableHandler.php +++ b/src/Message/Handler/CallableHandler.php @@ -9,15 +9,14 @@ /** * Handles a message by invoking the given callable. * - * @psalm-type MessageHandlerCallable = callable(MessageInterface $message): void + * @internal */ final class CallableHandler implements HandlerInterface { /** * @param callable $handler Callable invoked to handle a message. - * Format: `function (MessageInterface $message): void`. * - * @psalm-param MessageHandlerCallable $handler + * @psalm-param callable(MessageInterface): void $handler */ public function __construct( private readonly mixed $handler, diff --git a/src/Message/Handler/Resolver/HandlerNotFoundException.php b/src/Message/Handler/HandlerNotFoundException.php similarity index 91% rename from src/Message/Handler/Resolver/HandlerNotFoundException.php rename to src/Message/Handler/HandlerNotFoundException.php index 1a8bad67..5b2a898d 100644 --- a/src/Message/Handler/Resolver/HandlerNotFoundException.php +++ b/src/Message/Handler/HandlerNotFoundException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Queue\Message\Handler\Resolver; +namespace Yiisoft\Queue\Message\Handler; use LogicException; use Throwable; diff --git a/src/Message/Handler/HandlerResolver.php b/src/Message/Handler/HandlerResolver.php new file mode 100644 index 00000000..f1a1b116 --- /dev/null +++ b/src/Message/Handler/HandlerResolver.php @@ -0,0 +1,144 @@ + + */ + private array $cache = []; + + private readonly Injector $injector; + private readonly CallableFactory $callableFactory; + + /** + * @param (array|callable|HandlerInterface|string)[] $handlers Handler definitions indexed by message type. + * @param ContainerInterface $container Container used to resolve handlers. + * @param ContainerInterface|null $callableDependencyContainer Container used to resolve callable handler + * dependencies. If not set, the main container is used. + * + * @psalm-param array $handlers + */ + public function __construct( + private readonly array $handlers, + private readonly ContainerInterface $container, + ?ContainerInterface $callableDependencyContainer = null, + ) { + $this->injector = new Injector($callableDependencyContainer ?? $this->container); + $this->callableFactory = new CallableFactory($this->container); + } + + /** + * Get a handler for the given message type. + * + * @param string $messageType Message type. + * + * @throws HandlerNotFoundException If no handler exists for the message type. + * @throws InvalidHandlerConfigurationException If the handler definition is configured incorrectly. + * @throws ContainerExceptionInterface Error while retrieving the entry from container. + */ + public function resolve(string $messageType): HandlerInterface + { + if ($messageType === '') { + throw new LogicException('Message type cannot be empty.'); + } + + if (array_key_exists($messageType, $this->cache)) { + return $this->cache[$messageType]; + } + + $this->cache[$messageType] = $this->internalResolve($messageType); + + return $this->cache[$messageType]; + } + + /** + * @throws HandlerNotFoundException + * @throws InvalidHandlerConfigurationException + * @throws ContainerExceptionInterface + */ + private function internalResolve(string $messageType): HandlerInterface + { + $definition = $this->handlers[$messageType] ?? $messageType; + + if ($definition instanceof HandlerInterface) { + return $definition; + } + + if (is_string($definition)) { + return $this->getHandlerFromContainer($messageType, $definition); + } + + return $this->createCallableHandler($messageType, $definition); + } + + /** + * @throws HandlerNotFoundException + * @throws InvalidHandlerConfigurationException + * @throws ContainerExceptionInterface + */ + private function getHandlerFromContainer(string $messageType, string $id): HandlerInterface + { + if (!$this->container->has($id)) { + throw new HandlerNotFoundException($messageType); + } + + $handler = $this->container->get($id); + + if ($handler instanceof HandlerInterface) { + return $handler; + } + + if (is_callable($handler)) { + return $this->createCallableHandler($messageType, $handler); + } + + throw new InvalidHandlerConfigurationException( + $messageType, + sprintf( + 'Resolved from container handler should be an instance of "%s" or callable, got "%s".', + HandlerInterface::class, + get_debug_type($handler), + ), + ); + } + + /** + * @throws InvalidHandlerConfigurationException + * @throws ContainerExceptionInterface + */ + private function createCallableHandler(string $messageType, mixed $definition): CallableHandler + { + try { + $callable = $this->callableFactory->create($definition); + } catch (InvalidCallableConfigurationException $exception) { + throw new InvalidHandlerConfigurationException($messageType, $exception->getMessage(), $exception); + } + + $callable = function (MessageInterface $message) use ($callable): void { + $this->injector->invoke($callable, [$message]); + }; + + return new CallableHandler($callable); + } +} diff --git a/src/Message/Handler/InvalidHandlerConfigurationException.php b/src/Message/Handler/InvalidHandlerConfigurationException.php new file mode 100644 index 00000000..dd14bb2c --- /dev/null +++ b/src/Message/Handler/InvalidHandlerConfigurationException.php @@ -0,0 +1,27 @@ + Cache of resolved handlers */ - private array $cache = []; - - public function __construct( - /** @var array */ - private readonly array $handlers, - private readonly ContainerInterface $container, - private readonly CallableFactory $callableFactory, - ) {} - - public function resolve(string $messageType): HandlerInterface - { - if ($messageType === '') { - throw new HandlerNotFoundException($messageType); - } - - if (array_key_exists($messageType, $this->cache)) { - return $this->cache[$messageType]; - } - - $definition = $this->handlers[$messageType] ?? $messageType; - - if (is_string($definition) && $this->container->has($definition)) { - $resolved = $this->container->get($definition); - - if ($resolved instanceof HandlerInterface) { - return $this->cache[$messageType] = $resolved; - } - } - - try { - /** @psalm-var callable(MessageInterface): void $callable */ - $callable = $this->callableFactory->create($definition); - - return $this->cache[$messageType] = new CallableHandler($callable); - } catch (InvalidCallableConfigurationException $exception) { - throw new HandlerNotFoundException($messageType, 0, $exception); - } - } -} diff --git a/src/Message/Handler/Resolver/HandlerResolverInterface.php b/src/Message/Handler/Resolver/HandlerResolverInterface.php deleted file mode 100644 index ed9bdf85..00000000 --- a/src/Message/Handler/Resolver/HandlerResolverInterface.php +++ /dev/null @@ -1,22 +0,0 @@ -messagesProcessedSecond = []; $container = $this->createMock(ContainerInterface::class); - $callableFactory = new CallableFactory($container); $worker = new Worker( new NullLogger(), new ConsumeMiddlewareDispatcher($this->createMock(ConsumeMiddlewareFactoryInterface::class)), @@ -40,7 +39,6 @@ public function testMessagesConsumed(): void 'test2' => fn(MessageInterface $message): mixed => $this->messagesProcessedSecond[] = $message->getPayload(), ], $container, - $callableFactory, ), ); @@ -60,12 +58,11 @@ public function testMessagesConsumedByHandlerClass(): void $container = $this->createMock(ContainerInterface::class); $container->method('get')->with(TestHandler::class)->willReturn($handler); $container->method('has')->with(TestHandler::class)->willReturn(true); - $callableFactory = new CallableFactory($container); $worker = new Worker( new NullLogger(), new ConsumeMiddlewareDispatcher($this->createMock(ConsumeMiddlewareFactoryInterface::class)), new FailureMiddlewareDispatcher($this->createMock(FailureMiddlewareFactoryInterface::class), []), - new HandlerResolver([], $container, $callableFactory), + new HandlerResolver([], $container), ); $messages = [1, 'foo', 'bar-baz']; diff --git a/tests/Integration/MiddlewareTest.php b/tests/Integration/MiddlewareTest.php index bf1eadd6..50d4e7bb 100644 --- a/tests/Integration/MiddlewareTest.php +++ b/tests/Integration/MiddlewareTest.php @@ -25,7 +25,7 @@ use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactory; use Yiisoft\Queue\SyncQueueProducer; use Yiisoft\Queue\QueueProducerInterface; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; +use Yiisoft\Queue\Message\Handler\HandlerResolver; use Yiisoft\Queue\Tests\Integration\Support\TestMiddleware; use Yiisoft\Queue\Worker\Worker; use Yiisoft\Queue\Worker\WorkerInterface; @@ -107,7 +107,7 @@ public function testFullStackConsume(): void new SimpleLogger(), $consumeMiddlewareDispatcher, $failureMiddlewareDispatcher, - new HandlerResolver(['test' => static fn() => true], $container, $callableFactory), + new HandlerResolver(['test' => static fn() => true], $container), ); $message = new GenericMessage('test', ['initial']); diff --git a/tests/TestCase.php b/tests/TestCase.php index cb81a762..7b290272 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,7 +23,7 @@ use Yiisoft\Queue\Middleware\Push\PushMiddlewareFactory; use Yiisoft\Queue\AsyncQueueProducer; use Yiisoft\Queue\QueueProducerInterface; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; +use Yiisoft\Queue\Message\Handler\HandlerResolver; use Yiisoft\Queue\SyncQueueProducer; use Yiisoft\Queue\Worker\Worker; use Yiisoft\Queue\Worker\WorkerInterface; @@ -124,7 +124,6 @@ protected function createWorker(): WorkerInterface new HandlerResolver( $this->getMessageHandlers(), $this->getContainer(), - new CallableFactory($this->getContainer()), ), ); } diff --git a/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php b/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php index 94bd8911..f945e08d 100644 --- a/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php +++ b/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php @@ -4,11 +4,13 @@ namespace Yiisoft\Queue\Tests\Unit\Message\Handler\Resolver; +use LogicException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Yiisoft\Test\Support\Container\SimpleContainer; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerNotFoundException; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolver; +use Yiisoft\Queue\Message\Handler\HandlerNotFoundException; +use Yiisoft\Queue\Message\Handler\HandlerResolver; +use Yiisoft\Queue\Message\Handler\InvalidHandlerConfigurationException; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; use Yiisoft\Queue\Middleware\CallableFactory; @@ -22,7 +24,7 @@ public function testResolve(mixed $handler, array $containerServices): void { $message = new GenericMessage('simple', ['test-data']); $container = new SimpleContainer($containerServices); - $resolver = new HandlerResolver(['simple' => $handler], $container, new CallableFactory($container)); + $resolver = new HandlerResolver(['simple' => $handler], $container); $resolvedHandler = $resolver->resolve($message->getType()); $resolvedHandler->handle($message); @@ -62,7 +64,7 @@ function (MessageInterface $message) { public function testResolveCachesResolvedHandler(): void { $container = new SimpleContainer([FakeHandler::class => new FakeHandler()]); - $resolver = new HandlerResolver(['simple' => FakeHandler::class], $container, new CallableFactory($container)); + $resolver = new HandlerResolver(['simple' => FakeHandler::class], $container); $this->assertSame($resolver->resolve('simple'), $resolver->resolve('simple')); } @@ -73,7 +75,6 @@ public function testResolveStaticMethodHandler(): void $resolver = new HandlerResolver( ['static-handler' => StaticMessageHandler::handle(...)], $container, - new CallableFactory($container), ); StaticMessageHandler::$wasHandled = false; @@ -85,14 +86,13 @@ public function testResolveStaticMethodHandler(): void public function testResolveThrowsWhenDefinitionMethodUndefined(): void { - $this->expectException(HandlerNotFoundException::class); - $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); + $this->expectException(InvalidHandlerConfigurationException::class); + $this->expectExceptionMessage('Queue handler for message type "simple" is configured incorrectly'); $container = new SimpleContainer([FakeHandler::class => new FakeHandler()]); $resolver = new HandlerResolver( ['simple' => [FakeHandler::class, 'undefinedMethod']], $container, - new CallableFactory($container), ); $resolver->resolve('simple'); @@ -100,14 +100,13 @@ public function testResolveThrowsWhenDefinitionMethodUndefined(): void public function testResolveThrowsWhenDefinitionClassUndefined(): void { - $this->expectException(HandlerNotFoundException::class); - $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); + $this->expectException(InvalidHandlerConfigurationException::class); + $this->expectExceptionMessage('Queue handler for message type "simple" is configured incorrectly'); $container = new SimpleContainer([FakeHandler::class => new FakeHandler()]); $resolver = new HandlerResolver( ['simple' => ['UndefinedClass', 'handle']], $container, - new CallableFactory($container), ); $resolver->resolve('simple'); @@ -115,14 +114,13 @@ public function testResolveThrowsWhenDefinitionClassUndefined(): void public function testResolveThrowsWhenDefinitionClassNotFoundInContainer(): void { - $this->expectException(HandlerNotFoundException::class); - $this->expectExceptionMessage('Queue handler for message type "simple" does not exist'); + $this->expectException(InvalidHandlerConfigurationException::class); + $this->expectExceptionMessage('Queue handler for message type "simple" is configured incorrectly'); $container = new SimpleContainer(); $resolver = new HandlerResolver( ['simple' => [FakeHandler::class, 'handle']], $container, - new CallableFactory($container), ); $resolver->resolve('simple'); @@ -134,33 +132,33 @@ public function testResolveThrowsWhenHandlerNotFoundInContainer(): void $this->expectExceptionMessage('Queue handler for message type "nonexistent" does not exist'); $container = new SimpleContainer(); - $resolver = new HandlerResolver([], $container, new CallableFactory($container)); + $resolver = new HandlerResolver([], $container); $resolver->resolve('nonexistent'); } public function testResolveThrowsWhenHandlerInContainerNotImplementingInterface(): void { - $this->expectException(HandlerNotFoundException::class); - $this->expectExceptionMessage('Queue handler for message type "invalid" does not exist'); + $this->expectException(InvalidHandlerConfigurationException::class); + $this->expectExceptionMessage('Queue handler for message type "invalid" is configured incorrectly'); $container = new SimpleContainer([ 'invalid' => new class { public function handle(): void {} }, ]); - $resolver = new HandlerResolver([], $container, new CallableFactory($container)); + $resolver = new HandlerResolver([], $container); $resolver->resolve('invalid'); } public function testResolveThrowsWhenMessageTypeIsEmpty(): void { - $this->expectException(HandlerNotFoundException::class); - $this->expectExceptionMessage('Queue handler for message type "" does not exist'); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Message type cannot be empty.'); $container = new SimpleContainer(); - $resolver = new HandlerResolver([], $container, new CallableFactory($container)); + $resolver = new HandlerResolver([], $container); $resolver->resolve(''); } diff --git a/tests/Unit/WorkerTest.php b/tests/Unit/WorkerTest.php index 1ef46e84..a4f00d19 100644 --- a/tests/Unit/WorkerTest.php +++ b/tests/Unit/WorkerTest.php @@ -7,12 +7,13 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use RuntimeException; +use Yiisoft\Test\Support\Container\SimpleContainer; use Yiisoft\Test\Support\Log\SimpleLogger; use Yiisoft\Queue\Exception\MessageFailureException; -use Yiisoft\Queue\Message\Handler\CallableHandler; -use Yiisoft\Queue\Message\Handler\Resolver\HandlerResolverInterface; +use Yiisoft\Queue\Message\Handler\HandlerResolver; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; +use Yiisoft\Queue\Middleware\CallableFactory; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareFactoryInterface; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareInterface; @@ -108,17 +109,18 @@ public function testMessageFailureIsHandledSuccessfully(): void self::assertSame($finalMessage, $result); } - private function createHandlerResolver(MessageInterface $message, callable $handler): HandlerResolverInterface + private function createHandlerResolver(MessageInterface $message, callable $handler): HandlerResolver { - /** @var HandlerResolverInterface&MockObject $handlerResolver */ - $handlerResolver = $this->createMock(HandlerResolverInterface::class); - $handlerResolver->method('resolve')->with($message->getType())->willReturn(new CallableHandler($handler)); + $container = new SimpleContainer(); - return $handlerResolver; + return new HandlerResolver( + [$message->getType() => $handler], + $container, + ); } private function createWorkerByParams( - HandlerResolverInterface $handlerResolver, + HandlerResolver $handlerResolver, ?LoggerInterface $logger = null, ?ConsumeMiddlewareDispatcher $consumeMiddlewareDispatcher = null, ?FailureMiddlewareDispatcher $failureMiddlewareDispatcher = null, From da2d72bc3c8bbccfe8aadf8262c6a5201afe3c2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 18 Aug 2026 07:10:44 +0000 Subject: [PATCH 4/5] Apply PHP CS Fixer and Rector changes (CI) --- tests/Integration/MessageConsumingTest.php | 1 - tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php | 1 - tests/Unit/WorkerTest.php | 1 - 3 files changed, 3 deletions(-) diff --git a/tests/Integration/MessageConsumingTest.php b/tests/Integration/MessageConsumingTest.php index 1036791a..e978602f 100644 --- a/tests/Integration/MessageConsumingTest.php +++ b/tests/Integration/MessageConsumingTest.php @@ -8,7 +8,6 @@ use Psr\Log\NullLogger; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; -use Yiisoft\Queue\Middleware\CallableFactory; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareFactoryInterface; use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareDispatcher; diff --git a/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php b/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php index f945e08d..bd3e624e 100644 --- a/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php +++ b/tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php @@ -13,7 +13,6 @@ use Yiisoft\Queue\Message\Handler\InvalidHandlerConfigurationException; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; -use Yiisoft\Queue\Middleware\CallableFactory; use Yiisoft\Queue\Tests\App\FakeHandler; use Yiisoft\Queue\Tests\App\StaticMessageHandler; diff --git a/tests/Unit/WorkerTest.php b/tests/Unit/WorkerTest.php index a4f00d19..df648666 100644 --- a/tests/Unit/WorkerTest.php +++ b/tests/Unit/WorkerTest.php @@ -13,7 +13,6 @@ use Yiisoft\Queue\Message\Handler\HandlerResolver; use Yiisoft\Queue\Message\GenericMessage; use Yiisoft\Queue\Message\MessageInterface; -use Yiisoft\Queue\Middleware\CallableFactory; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareFactoryInterface; use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareInterface; From 89789bc77bc9b3d0290b2253dd452c816257d898 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 18 Aug 2026 10:12:27 +0300 Subject: [PATCH 5/5] fix --- tests/Benchmark/QueueBench.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Benchmark/QueueBench.php b/tests/Benchmark/QueueBench.php index e14a128b..d3a75ca4 100644 --- a/tests/Benchmark/QueueBench.php +++ b/tests/Benchmark/QueueBench.php @@ -54,7 +54,6 @@ public function __construct() 'foo' => static function (): void {}, ], $container, - $callableFactory, ), ); $this->serializer = new MessageSerializer(new JsonMessageEncoder());