Skip to content

Commit

Permalink
long running process doctrine connection listener
Browse files Browse the repository at this point in the history
  • Loading branch information
alli83 committed Mar 20, 2024
1 parent f966567 commit a9fb4dc
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
4 changes: 4 additions & 0 deletions DependencyInjection/DoctrineExtension.php
Expand Up @@ -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')) {

Check failure on line 112 in DependencyInjection/DoctrineExtension.php

View workflow job for this annotation

GitHub Actions / Coding Standards / Coding Standards (8.2)

Expected 1 space after NOT operator; 0 found

Check failure on line 112 in DependencyInjection/DoctrineExtension.php

View workflow job for this annotation

GitHub Actions / Coding Standards / Coding Standards (8.2)

Expected 1 space after NOT operator; 0 found
$container->removeDefinition('doctrine.connection.keep.alive_middleware');
}

if (empty($config['orm'])) {
return;
}
Expand Down
5 changes: 5 additions & 0 deletions Resources/config/middlewares.xml
Expand Up @@ -17,5 +17,10 @@
<argument type="service" id="doctrine.debug_data_holder" />
<argument type="service" id="debug.stopwatch" on-invalid="null" />
</service>
<service id="doctrine.connection.keep.alive_middleware" class="Symfony\Bridge\Doctrine\Middleware\ConnectionKeepAlive">
<argument type="service" id="doctrine" />
<argument type="service" id="service_container" />
<tag name="doctrine.middleware" />
</service>
</services>
</container>
182 changes: 182 additions & 0 deletions Tests/ConnectionKeepAliveMiddlewareTest.php
@@ -0,0 +1,182 @@
<?php

declare(strict_types=1);

namespace Doctrine\Bundle\DoctrineBundle\Tests;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Result;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\CheckConnection;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Based on https://github.com/Baldinof/roadrunner-bundle/blob/3.x/src/Integration/Doctrine/DoctrineORMMiddleware.php
*/
class ConnectionKeepAliveMiddlewareTest extends TestCase
{
public const CONNECTION_NAME = 'doctrine.connection';
public const MANAGER_NAME = 'doctrine.manager';

private MockObject&ManagerRegistry $managerRegistryMock;
private MockObject&Connection $connectionMock;
private ContainerInterface $container;
private MockObject&Driver $driver;
private DoctrineConnectionDriverTest $doctrineConnectionDriver;

public function setUp(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$platform->method('getDummySelectSQL')->willReturn('SELECT 1');

$this->managerRegistryMock = $this->createMock(ManagerRegistry::class);
$this->connectionMock = $this->createMock(Connection::class);

Check failure on line 42 in Tests/ConnectionKeepAliveMiddlewareTest.php

View workflow job for this annotation

GitHub Actions / Coding Standards / Coding Standards (8.2)

Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space
$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)

Check failure on line 130 in Tests/ConnectionKeepAliveMiddlewareTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm

ContainerDependency

Tests/ConnectionKeepAliveMiddlewareTest.php:130:83: ContainerDependency: Container must not inject into services as dependency! Use dependency-injection.
{
$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);

Check failure on line 158 in Tests/ConnectionKeepAliveMiddlewareTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis with Psalm

UndefinedClass

Tests/ConnectionKeepAliveMiddlewareTest.php:158:17: UndefinedClass: Class, interface or enum named Symfony\Bridge\Doctrine\CheckConnection does not exist (see https://psalm.dev/019)
}

$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()) {

Check failure on line 174 in Tests/ConnectionKeepAliveMiddlewareTest.php

View workflow job for this annotation

GitHub Actions / Coding Standards / Coding Standards (8.2)

Use early exit to reduce code nesting.
$this->managerRegistry->resetManager($managerName);
}
}
}

return parent::connect($params);
}
}

0 comments on commit a9fb4dc

Please sign in to comment.