-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Description
Symfony version(s) affected
6.4.0 up to 7.3.4 (I'm running 6.4.11)
Description
Throwing a PDOException inside a message handler causes an unexpected TypeError when trying to wrap it inside RunCommandFailedException.
This happens, because PDOException does not follow Liskov Substitution Principle and has string as return type of getCode() even though its parent, RuntimeException::getType() return type is int.
The impact of this is that it's very difficult to figure out the underlying error, because TypeError masks it.
How to reproduce
- Create a message handler.
- Connect to MySQL and make a non-sensical query. For example, put the following to a message handler (assuming you have mysql listening on 127.0.0.1:3306).
$pdo = new \PDO('mysql:host=127.0.0.1', 'root', 'root'); $pdo->query('select x');
- Send a message into the queue that would trigger the handler.
- Observe the logs.
CRITICAL [messenger] Error thrown while handling message Symfony\Component\Console\Messenger\RunCommandMessage. Removing from transport after 4 retries. Error: "Handling "Symfony\Component\Console\Messenger\RunCommandMessage" failed: Exception::__construct(): Argument #2 ($code) must be of type int, string given" ["class" => "Symfony\Component\Console\Messenger\RunCommandMessage","retryCount" => 4,"error" => "Handling "Symfony\Component\Console\Messenger\RunCommandMessage" failed: Exception::__construct(): Argument #2 ($code) must be of type int, string given","exception" => Symfony\Component\Messenger\Exception\HandlerFailedException { …}]
Possible Solution
I propose the following change to RunCommandFailedException to work around PDO specifically.
diff --git a/src/Symfony/Component/Console/Exception/RunCommandFailedException.php b/src/Symfony/Component/Console/Exception/RunCommandFailedException.php
index 5d87ec949a..527997c0b8 100644
--- a/src/Symfony/Component/Console/Exception/RunCommandFailedException.php
+++ b/src/Symfony/Component/Console/Exception/RunCommandFailedException.php
@@ -20,10 +20,17 @@ final class RunCommandFailedException extends RuntimeException
{
public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context)
{
+ if ($exception instanceof \PDOException) {
+ $code = 0;
+ } else {
+ $code = $exception instanceof \Throwable ? $exception->getCode() : 0;
+ }
+
parent::__construct(
$exception instanceof \Throwable ? $exception->getMessage() : $exception,
- $exception instanceof \Throwable ? $exception->getCode() : 0,
+ $code,
$exception instanceof \Throwable ? $exception : null,
);
}
}Additional Context
This problem has already been reported to upstream as php/php-src#9529.
Full stack trace.
TypeError: Exception::__construct(): Argument #2 ($code) must be of type int, string given
#0 /vendor/symfony/console/Exception/RunCommandFailedException.php(23): Exception::__construct
#1 /vendor/symfony/console/Exception/RunCommandFailedException.php(23): Symfony\Component\Console\Exception\RunCommandFailedException::__construct
#2 /vendor/symfony/console/Messenger/RunCommandMessageHandler.php(39): Symfony\Component\Console\Messenger\RunCommandMessageHandler::__invoke
#3 /vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(152): Symfony\Component\Messenger\Middleware\HandleMessageMiddleware::callHandler
#4 /vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(91): Symfony\Component\Messenger\Middleware\HandleMessageMiddleware::handle
#5 /vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\Component\Messenger\Middleware\SendMessageMiddleware::handle
#6 ... (omitted, irrelevant to the bug report)
#7 ... (omitted, irrelevant to the bug report)
#8 /vendor/symfony/doctrine-bridge/Messenger/DoctrineCloseConnectionMiddleware.php(31): Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware::handleForManager
#9 /vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php(45): Symfony\Bridge\Doctrine\Messenger\AbstractDoctrineMiddleware::handle
#10 /vendor/symfony/doctrine-bridge/Messenger/DoctrinePingConnectionMiddleware.php(34): Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware::handleForManager
#11 /vendor/symfony/doctrine-bridge/Messenger/AbstractDoctrineMiddleware.php(45): Symfony\Bridge\Doctrine\Messenger\AbstractDoctrineMiddleware::handle
#12 /vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware::handle
#13 /vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware::handle
#14 /vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware::handle
#15 /vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware::handle
#16 /vendor/symfony/messenger/MessageBus.php(70): Symfony\Component\Messenger\MessageBus::dispatch
#17 /vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\Component\Messenger\RoutableMessageBus::dispatch
#18 /vendor/symfony/messenger/Worker.php(161): Symfony\Component\Messenger\Worker::handleMessage
#19 /vendor/symfony/messenger/Worker.php(108): Symfony\Component\Messenger\Worker::run
#20 /vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(238): Symfony\Component\Messenger\Command\ConsumeMessagesCommand::execute
#21 /vendor/symfony/console/Command/Command.php(326): Symfony\Component\Console\Command\Command::run
#22 /vendor/symfony/console/Application.php(1096): Symfony\Component\Console\Application::doRunCommand
#23 /vendor/symfony/framework-bundle/Console/Application.php(126): Symfony\Bundle\FrameworkBundle\Console\Application::doRunCommand
#24 /vendor/symfony/console/Application.php(324): Symfony\Component\Console\Application::doRun
#25 /vendor/symfony/framework-bundle/Console/Application.php(80): Symfony\Bundle\FrameworkBundle\Console\Application::doRun
#26 /vendor/symfony/console/Application.php(175): Symfony\Component\Console\Application::run
#27 /vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner::run
#28 /vendor/autoload_runtime.php(29): require_once
#29 /bin/console(15)