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