From 838846a82017904a58f299a03423309d4a350586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 18 May 2021 16:36:58 +0200 Subject: [PATCH] [Messenger] Add a middleware to log when transaction has been left open --- src/Symfony/Bridge/Doctrine/CHANGELOG.md | 4 ++ ...octrineOpenTransactionLoggerMiddleware.php | 49 +++++++++++++++ ...ineOpenTransactionLoggerMiddlewareTest.php | 62 +++++++++++++++++++ src/Symfony/Bridge/Doctrine/composer.json | 3 +- 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineOpenTransactionLoggerMiddlewareTest.php diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 6323313ba9b89..03d91b380d9ab 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +5.4 +--- + * Add a middleware to log when transaction has been left open `DoctrineOpenTransactionLoggerMiddleware` + 5.3 --- diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php new file mode 100644 index 0000000000000..246f0090e58ef --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineOpenTransactionLoggerMiddleware.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Messenger; + +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Middleware\StackInterface; + +/** + * Middleware to log when transaction has been left open. + * + * @author Grégoire Pineau + */ +class DoctrineOpenTransactionLoggerMiddleware extends AbstractDoctrineMiddleware +{ + private $logger; + + public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null, LoggerInterface $logger = null) + { + parent::__construct($managerRegistry, $entityManagerName); + + $this->logger = $logger ?? new NullLogger(); + } + + protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope + { + try { + return $stack->next()->handle($envelope, $stack); + } finally { + if ($entityManager->getConnection()->isTransactionActive()) { + $this->logger->error('A handler opened a transaction but did not close it.', [ + 'message' => $envelope->getMessage(), + ]); + } + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineOpenTransactionLoggerMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineOpenTransactionLoggerMiddlewareTest.php new file mode 100644 index 0000000000000..626c19eb4ceae --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineOpenTransactionLoggerMiddlewareTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Messenger; + +use Doctrine\DBAL\Connection; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\AbstractLogger; +use Symfony\Bridge\Doctrine\Messenger\DoctrineOpenTransactionLoggerMiddleware; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; + +class DoctrineOpenTransactionLoggerMiddlewareTest extends MiddlewareTestCase +{ + private $logger; + private $connection; + private $entityManager; + private $middleware; + + protected function setUp(): void + { + $this->logger = new class() extends AbstractLogger { + public $logs = []; + + public function log($level, $message, $context = []): void + { + $this->logs[$level][] = $message; + } + }; + + $this->connection = $this->createMock(Connection::class); + + $this->entityManager = $this->createMock(EntityManagerInterface::class); + $this->entityManager->method('getConnection')->willReturn($this->connection); + + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry->method('getManager')->willReturn($this->entityManager); + + $this->middleware = new DoctrineOpenTransactionLoggerMiddleware($managerRegistry, null, $this->logger); + } + + public function testMiddlewareWrapsInTransactionAndFlushes() + { + $this->connection->expects($this->exactly(1)) + ->method('isTransactionActive') + ->will($this->onConsecutiveCalls(true, true, false)) + ; + + $this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock()); + + $this->assertSame(['error' => ['A handler opened a transaction but did not close it.']], $this->logger->logs); + } +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 4676ecd7211c5..a4f5985ba6db6 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -48,7 +48,8 @@ "doctrine/collections": "~1.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13|^3.0", - "doctrine/orm": "^2.7.3" + "doctrine/orm": "^2.7.3", + "psr/log": "^1|^2|^3" }, "conflict": { "doctrine/dbal": "<2.13",