diff --git a/DependencyInjection/DoctrineExtension.php b/DependencyInjection/DoctrineExtension.php index 044235f3..97857911 100644 --- a/DependencyInjection/DoctrineExtension.php +++ b/DependencyInjection/DoctrineExtension.php @@ -109,6 +109,10 @@ public function load(array $configs, ContainerBuilder $container) $this->loadMessengerServices($container); } + if (! $container->hasParameter('kernel.runtime_mode') || ! $container->hasParameter('kernel.runtime_mode.worker')) { + $container->removeDefinition('doctrine.connection.keep.alive_middleware'); + } + if (empty($config['orm'])) { return; } diff --git a/Resources/config/middlewares.xml b/Resources/config/middlewares.xml index d6bf92bc..610f8c2f 100644 --- a/Resources/config/middlewares.xml +++ b/Resources/config/middlewares.xml @@ -17,5 +17,10 @@ + + + + + diff --git a/Tests/ConnectionKeepAliveMiddlewareTest.php b/Tests/ConnectionKeepAliveMiddlewareTest.php new file mode 100644 index 00000000..63355f5e --- /dev/null +++ b/Tests/ConnectionKeepAliveMiddlewareTest.php @@ -0,0 +1,182 @@ +createMock(AbstractPlatform::class); + $platform->method('getDummySelectSQL')->willReturn('SELECT 1'); + + $this->managerRegistryMock = $this->createMock(ManagerRegistry::class); + $this->connectionMock = $this->createMock(Connection::class); + $this->connectionMock->method('getDatabasePlatform')->willReturn($platform); + $connectionDriverMock = $this->createMock(Driver\Connection::class); + + $this->container = new Container(); + $this->container->set(self::CONNECTION_NAME, $this->connectionMock); + + $this->managerRegistryMock->method('getConnectionNames')->willReturn([self::CONNECTION_NAME]); + $this->managerRegistryMock->method('getManagerNames')->willReturn([self::MANAGER_NAME]); + + $this->driver = $this->createMock(Driver::class); + $this->driver->method('connect')->willReturn($connectionDriverMock); + + $this->doctrineConnectionDriver = new DoctrineConnectionDriverTest($this->driver, $this->managerRegistryMock, $this->container); + } + + public function testSkipNotInitializedConnections() + { + $this->container->set(self::CONNECTION_NAME, null); + + $this->connectionMock->expects($this->never())->method('isConnected'); + $this->connectionMock->expects($this->never())->method('executeQuery'); + $this->connectionMock->expects($this->never())->method('close'); + $this->connectionMock->expects($this->never())->method('connect'); + + $this->doctrineConnectionDriver->connect([]); + } + + public function testSkipWhenNotConnected(): void + { + $this->connectionMock->method('isConnected')->willReturn(false); + $this->connectionMock->expects($this->never())->method('executeQuery'); + $this->connectionMock->expects($this->never())->method('close'); + $this->connectionMock->expects($this->never())->method('connect'); + + $this->doctrineConnectionDriver->connect([]); + } + + public function testItClosesNotPingableConnection(): void + { + $this->connectionMock->expects($this->exactly(2))->method('executeQuery') + ->willReturnCallback(function () { + static $counter = 0; + + if (1 === ++$counter) { + throw $this->createMock(DBALException::class); + } + + return $this->createMock(Result::class); + }); + + $this->connectionMock->method('isConnected')->willReturn(true); + $this->connectionMock->expects($this->once())->method('close'); + $this->connectionMock->expects($this->never())->method('connect'); + + $this->doctrineConnectionDriver->connect([]); + } + + public function testItDoesNotClosePingableConnection(): void + { + $this->connectionMock->expects($this->once())->method('executeQuery'); + $this->connectionMock->method('isConnected')->willReturn(true); + $this->connectionMock->expects($this->never())->method('close'); + $this->connectionMock->expects($this->never())->method('connect'); + + $this->doctrineConnectionDriver->connect([]); + } + + public function testItForcesRebootOnClosedManagerWhenMissingProxySupport() + { + $manager = $this->createMock(EntityManagerInterface::class); + $this->container->set(self::MANAGER_NAME, $manager); + + $manager->expects($this->once())->method('isOpen')->willReturn(false); + $this->managerRegistryMock->expects($this->once()) + ->method('resetManager') + ->with(self::MANAGER_NAME); + + $this->doctrineConnectionDriver->connect([]); + } +} + +class DoctrineConnectionDriverTest extends AbstractDriverMiddleware +{ + private Driver $driver; + private ManagerRegistry $managerRegistry; + private ContainerInterface $container; + + public function __construct(Driver $driver, ManagerRegistry $managerRegistry, ContainerInterface $container) + { + $this->driver = $driver; + $this->managerRegistry = $managerRegistry; + $this->container = $container; + + parent::__construct($driver); + } + + /** + * {@inheritDoc} + */ + public function connect(array $params): DriverConnection + { + $connectionServices = $this->managerRegistry->getConnectionNames(); + + foreach ($connectionServices as $connectionServiceName) { + if (! $this->container->initialized($connectionServiceName)) { + continue; + } + + $connection = $this->container->get($connectionServiceName); + + if (! $connection instanceof Connection) { + continue; + } + + if ($connection->isConnected()) { + CheckConnection::reconnectOnFailure($connection); + } + + $managerNames = $this->managerRegistry->getManagerNames(); + + foreach ($managerNames as $managerName) { + if (! $this->container->initialized($managerName)) { + continue; + } + + $manager = $this->container->get($managerName); + + if (! $manager instanceof EntityManagerInterface) { + continue; + } + + if (! $manager->isOpen()) { + $this->managerRegistry->resetManager($managerName); + } + } + } + + return parent::connect($params); + } +}