From f5878a5e7b55d56eb7d9ad3b2174a1663e9d17b1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 31 Jul 2019 20:54:41 +0200 Subject: [PATCH] Ensured EntityManager is reopened by CloseDbConnectionMiddleware after an error closed it --- .../CloseDbConnectionMiddleware.php | 12 ++++++++ .../CloseDbConnectionMiddlewareTest.php | 29 +++++++++++++++++++ phpstan.neon | 3 ++ 3 files changed, 44 insertions(+) diff --git a/module/Common/src/Middleware/CloseDbConnectionMiddleware.php b/module/Common/src/Middleware/CloseDbConnectionMiddleware.php index 1294848d..a407a31a 100644 --- a/module/Common/src/Middleware/CloseDbConnectionMiddleware.php +++ b/module/Common/src/Middleware/CloseDbConnectionMiddleware.php @@ -3,11 +3,13 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Middleware; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Throwable; class CloseDbConnectionMiddleware implements MiddlewareInterface { @@ -23,6 +25,16 @@ class CloseDbConnectionMiddleware implements MiddlewareInterface { try { return $handler->handle($request); + } catch (Throwable $e) { + // FIXME Mega ugly hack to avoid a closed EntityManager to make shlink fail forever on swoole contexts + // Should be fixed with request-shared EntityManagers, which is not supported by the ServiceManager + if (! $this->em->isOpen()) { + (function () { + $this->closed = false; + })->bindTo($this->em, EntityManager::class)(); + } + + throw $e; } finally { $this->em->getConnection()->close(); $this->em->clear(); diff --git a/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php b/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php index 2bf93a85..34d141ad 100644 --- a/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php +++ b/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php @@ -10,6 +10,7 @@ use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use RuntimeException; use Shlinkio\Shlink\Common\Middleware\CloseDbConnectionMiddleware; +use Throwable; use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequest; @@ -34,6 +35,7 @@ class CloseDbConnectionMiddlewareTest extends TestCase $this->em->getConnection()->willReturn($this->conn->reveal()); $this->em->clear()->will(function () { }); + $this->em->isOpen()->willReturn(true); $this->middleware = new CloseDbConnectionMiddleware($this->em->reveal()); } @@ -69,4 +71,31 @@ class CloseDbConnectionMiddlewareTest extends TestCase $this->middleware->process($req, $this->handler->reveal()); } + + /** + * @test + * @dataProvider provideClosed + */ + public function entityManagerIsReopenedAfterAnExceptionWhichClosedIt(bool $closed): void + { + $req = new ServerRequest(); + $expectedError = new RuntimeException(); + $this->handler->handle($req)->willThrow($expectedError) + ->shouldBeCalledOnce(); + $this->em->closed = $closed; + $this->em->isOpen()->willReturn(false); + + try { + $this->middleware->process($req, $this->handler->reveal()); + $this->fail('Expected exception to be thrown but it did not.'); + } catch (Throwable $e) { + $this->assertSame($expectedError, $e); + $this->assertFalse($this->em->closed); + } + } + + public function provideClosed(): iterable + { + return [[true, false]]; + } } diff --git a/phpstan.neon b/phpstan.neon index 7756bc14..c735bfec 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,3 +3,6 @@ parameters: - '#League\\Plates\\callback#' - '#is not subtype of Throwable#' - '#ObjectManager::flush()#' + - + message: '#Access to an undefined property#' + path: %currentWorkingDirectory%/module/Common/src/Middleware/CloseDbConnectionMiddleware.php