8000 [Messenger] Add a middleware to ensure all transaction has been closed · symfony/symfony@230bede · GitHub
[go: up one dir, main page]

Skip to content

Commit 230bede

Browse files
committed
[Messenger] Add a middleware to ensure all transaction has been closed
1 parent 1629b59 commit 230bede

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

src/Symfony/Bridge/Doctrine/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
CHANGELOG
22
=========
33

4+
5.4
5+
---
6+
* Add a middleware to ensure all transaction has been closed `DoctrineTransactionWatchdogMiddleware`
7+
48
5.3
59
---
610

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Messenger;
13+
14+
use Doctrine\ORM\EntityManagerInterface;
15+
use Doctrine\Persistence\ManagerRegistry;
16+
use Psr\Log\LoggerInterface;
17+
use Psr\Log\NullLogger;
18+
use Symfony\Component\Messenger\Envelope;
19+
use Symfony\Component\Messenger\Middleware\StackInterface;
20+
21+
/**
22+
* Search for open transactions, then log and close them.
23+
*
24+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
25+
*/
26+
class DoctrineTransactionWatchdogMiddleware extends AbstractDoctrineMiddleware
27+
{
28+
private $logger;
29+
30+
public function __construct(ManagerRegistry $managerRegistry, string $entityManagerName = null, LoggerInterface $logger = null)
31+
{
32+
parent::__construct($managerRegistry, $entityManagerName);
33+
34+
$this->logger = $logger ?? new NullLogger();
35+
}
36+
37+
protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope
38+
{
39+
try {
40+
return $stack->next()->handle($envelope, $stack);
41+
} finally {
42+
if ($entityManager->getConnection()->isTransactionActive()) {
43+
$this->logger->error('A handler opened a transaction but did not close it.', [
44+
'message' => $envelope->getMessage(),
45+
]);
46+
47+
$connection = $entityManager->getConnection();
48+
while ($connection->isTransactionActive()) {
49+
$connection->rollBack();
50+
}
51+
}
52+
}
53+
}
54+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\Messenger;
13+
14+
use Doctrine\DBAL\Connection;
15+
use Doctrine\ORM\EntityManagerInterface;
16+
use Doctrine\Persistence\ManagerRegistry;
17+
use Psr\Log\AbstractLogger;
18+
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionWatchdogMiddleware;
19+
use Symfony\Component\Messenger\Envelope;
20+
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
21+
22+
class DoctrineTransactionWatchdogMiddlewareTest extends MiddlewareTestCase
23+
{
24+
private $logger;
25+
private $connection;
26+
private $entityManager;
27+
private $middleware;
28+
29+
protected function setUp(): void
30+
{
31+
$this->logger = new class extends AbstractLogger
32+
{
33+
public $logs = [];
34+
public function log($level, $message, $context = [])
35+
{
36+
$this->logs[$level][] = $message;
37+
}
38+
};
39+
40+
$this->connection = $this->createMock(Connection::class);
41+
42+
$this->entityManager = $this->createMock(EntityManagerInterface::class);
43+
$this->entityManager->method('getConnection')->willReturn($this->connection);
44+
45+
$managerRegistry = $this->createMock(ManagerRegistry::class);
46+
$managerRegistry->method('getManager')->willReturn($this->entityManager);
47+
48+
$this->middleware = new DoctrineTransactionWatchdogMiddleware($managerRegistry, null, $this->logger);
49+
}
50+
51+
public function testMiddlewareWrapsInTransactionAndFlushes()
52+
{
53+
$this->connection->expects($this->exactly(3))
54+
->method('isTransactionActive')
55+
->will($this->onConsecutiveCalls(true, true, false))
56+
;
57+
$this->connection->expects($this->exactly(1))
58+
->method('rollBack')
59+
;
60+
61+
$this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock());
62+
63+
$this->assertSame(['error' => ['A handler opened a transaction but did not close it.']], $this->logger->logs);
64+
}
65+
}

src/Symfony/Bridge/Doctrine/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848 402E
"doctrine/collections": "~1.0",
4949
"doctrine/data-fixtures": "^1.1",
5050
"doctrine/dbal": "^2.10|^3.0",
51-
"doctrine/orm": "^2.7.3"
51+
"doctrine/orm": "^2.7.3",
52+
"psr/log": "^1|^2|^3"
5253
},
5354
"conflict": {
5455
"doctrine/dbal": "<2.10",

0 commit comments

Comments
 (0)
0