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)
Symfony version(s) affected
6.4.0 up to 7.3.4 (I'm running 6.4.11)
Description
Throwing a
PDOExceptioninside a message handler causes an unexpectedTypeErrorwhen trying to wrap it insideRunCommandFailedException.This happens, because
PDOExceptiondoes not follow Liskov Substitution Principle and hasstringas return type ofgetCode()even though its parent,RuntimeException::getType()return type isint.The impact of this is that it's very difficult to figure out the underlying error, because
TypeErrormasks it.How to reproduce
Possible Solution
I propose the following change to
RunCommandFailedExceptionto work around PDO specifically.Additional Context
This problem has already been reported to upstream as php/php-src#9529.
Full stack trace.