diff --git a/config/dbal.xml b/config/dbal.xml
index a38d0de9..61fcb5ed 100644
--- a/config/dbal.xml
+++ b/config/dbal.xml
@@ -101,6 +101,12 @@
+
+
+
+
+
+
diff --git a/config/middlewares.xml b/config/middlewares.xml
index d6bf92bc..28c3c330 100644
--- a/config/middlewares.xml
+++ b/config/middlewares.xml
@@ -5,6 +5,7 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
@@ -17,5 +18,9 @@
+
+
+
+
diff --git a/psalm.xml.dist b/psalm.xml.dist
index 9a319d21..e0bafcb1 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -45,6 +45,11 @@
+
+
+
+
+
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index ba0fed1d..65c680df 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -221,6 +221,7 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition
->end()
->booleanNode('disable_type_comments')->end()
->scalarNode('server_version')->end()
+ ->integerNode('idle_connection_ttl')->defaultValue(600)->end()
->scalarNode('driver_class')->end()
->scalarNode('wrapper_class')->end()
->booleanNode('keep_slave')
diff --git a/src/DependencyInjection/DoctrineExtension.php b/src/DependencyInjection/DoctrineExtension.php
index 161626a7..d61ebc83 100644
--- a/src/DependencyInjection/DoctrineExtension.php
+++ b/src/DependencyInjection/DoctrineExtension.php
@@ -37,6 +37,7 @@
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
+use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener;
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
use Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaListener;
use Symfony\Bridge\Doctrine\SchemaListener\LockStoreSchemaListener;
@@ -83,7 +84,7 @@
*
* @final since 2.9
* @psalm-type DBALConfig = array{
- * connections: array,
+ * connections: array,
* driver_schemes: array,
* default_connection: string,
* types: array,
@@ -196,6 +197,9 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
$connWithLogging = [];
$connWithProfiling = [];
$connWithBacktrace = [];
+ $ttlByConnection = [];
+ $connWithTtl = [];
+
foreach ($config['connections'] as $name => $connection) {
if ($connection['logging']) {
$connWithLogging[] = $name;
@@ -209,6 +213,11 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
}
}
+ if ($connection['idle_connection_ttl'] > 0) {
+ $connWithTtl[] = $name;
+ $ttlByConnection[$name] = $connection['idle_connection_ttl'];
+ }
+
$this->loadDbalConnection($name, $connection, $container);
}
@@ -228,7 +237,16 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
}
});
- $this->registerDbalMiddlewares($container, $connWithLogging, $connWithProfiling, $connWithBacktrace);
+ $this->registerDbalMiddlewares($container, $connWithLogging, $connWithProfiling, $connWithBacktrace, $connWithTtl);
+
+ $container->getDefinition('doctrine.dbal.idle_connection_middleware')->setArgument(1, $ttlByConnection);
+
+ if (class_exists(Listener::class)) {
+ return;
+ }
+
+ $container->removeDefinition('doctrine.dbal.idle_connection_listener');
+ $container->removeDefinition('doctrine.dbal.idle_connection_middleware');
}
/**
@@ -1186,12 +1204,14 @@ private function createArrayAdapterCachePool(ContainerBuilder $container, string
* @param string[] $connWithLogging
* @param string[] $connWithProfiling
* @param string[] $connWithBacktrace
+ * @param string[] $connWithTtl
*/
private function registerDbalMiddlewares(
ContainerBuilder $container,
array $connWithLogging,
array $connWithProfiling,
- array $connWithBacktrace
+ array $connWithBacktrace,
+ array $connWithTtl
): void {
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
$loader->load('middlewares.xml');
@@ -1207,5 +1227,11 @@ private function registerDbalMiddlewares(
$debugMiddlewareAbstractDef
->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]);
}
+
+ $idleConnectionMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.idle_connection_middleware');
+ foreach ($connWithTtl as $connName) {
+ $idleConnectionMiddlewareAbstractDef
+ ->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]);
+ }
}
}
diff --git a/src/Middleware/IdleConnectionMiddleware.php b/src/Middleware/IdleConnectionMiddleware.php
new file mode 100644
index 00000000..001caa36
--- /dev/null
+++ b/src/Middleware/IdleConnectionMiddleware.php
@@ -0,0 +1,36 @@
+ */
+ private array $ttlByConnection;
+ private string $connectionName;
+
+ /**
+ * @param ArrayObject $connectionExpiries
+ * @param array $ttlByConnection
+ */
+ public function __construct(ArrayObject $connectionExpiries, array $ttlByConnection)
+ {
+ $this->connectionExpiries = $connectionExpiries;
+ $this->ttlByConnection = $ttlByConnection;
+ }
+
+ public function setConnectionName(string $name): void
+ {
+ $this->connectionName = $name;
+ }
+
+ public function wrap(Driver $driver): Driver
+ {
+ return new IdleConnectionDriver($driver, $this->connectionExpiries, $this->ttlByConnection[$this->connectionName], $this->connectionName);
+ }
+}
diff --git a/tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/tests/DependencyInjection/AbstractDoctrineExtensionTest.php
index 58c0fcf3..60daad64 100644
--- a/tests/DependencyInjection/AbstractDoctrineExtensionTest.php
+++ b/tests/DependencyInjection/AbstractDoctrineExtensionTest.php
@@ -220,6 +220,7 @@ public function testDbalLoadSinglePrimaryReplicaConnection(): void
'host' => 'localhost',
'unix_socket' => '/path/to/mysqld.sock',
'driverOptions' => [PDO::ATTR_STRINGIFY_FETCHES => 1],
+ 'idle_connection_ttl' => 600,
],
$param['primary'],
);
@@ -340,6 +341,7 @@ public function testLoadSimpleSingleConnection(): void
'driver' => 'pdo_mysql',
'driverOptions' => [],
'defaultTableOptions' => [],
+ 'idle_connection_ttl' => 600,
],
new Reference('doctrine.dbal.default_connection.configuration'),
method_exists(Connection::class, 'getEventManager')
@@ -379,6 +381,7 @@ public function testLoadSimpleSingleConnectionWithoutDbName(): void
'driver' => 'pdo_mysql',
'driverOptions' => [],
'defaultTableOptions' => [],
+ 'idle_connection_ttl' => 600,
],
new Reference('doctrine.dbal.default_connection.configuration'),
method_exists(Connection::class, 'getEventManager')
@@ -418,6 +421,7 @@ public function testLoadSingleConnection(): void
'dbname' => 'sqlite_db',
'memory' => true,
'defaultTableOptions' => [],
+ 'idle_connection_ttl' => 600,
],
new Reference('doctrine.dbal.default_connection.configuration'),
method_exists(Connection::class, 'getEventManager')
diff --git a/tests/DependencyInjection/Compiler/MiddlewarePassTest.php b/tests/DependencyInjection/Compiler/MiddlewarePassTest.php
index 06dc92cb..acb64f93 100644
--- a/tests/DependencyInjection/Compiler/MiddlewarePassTest.php
+++ b/tests/DependencyInjection/Compiler/MiddlewarePassTest.php
@@ -6,15 +6,18 @@
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\MiddlewaresPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension;
use Doctrine\Bundle\DoctrineBundle\Middleware\ConnectionNameAwareInterface;
+use Doctrine\Bundle\DoctrineBundle\Middleware\IdleConnectionMiddleware;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Middleware;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
+use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use function array_map;
+use function class_exists;
use function implode;
use function sprintf;
@@ -170,7 +173,8 @@ public function testAddMiddlewareOrderingWithDefaultPriority(): void
$this->assertMiddlewareInjected($container, 'conn1', PHP7Middleware::class);
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
- $this->assertMiddlewareOrdering($container, 'conn1', [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class]);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, PHP7Middleware::class, ConnectionAwarePHP7Middleware::class] : [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class];
+ $this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
}
public function testAddMiddlewareOrderingWithExplicitPriority(): void
@@ -193,7 +197,8 @@ public function testAddMiddlewareOrderingWithExplicitPriority(): void
$this->assertMiddlewareInjected($container, 'conn1', PHP7Middleware::class);
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
- $this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class]);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class];
+ $this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
}
public function testAddMiddlewareOrderingWithExplicitPriorityAndConnection(): void
@@ -222,7 +227,8 @@ public function testAddMiddlewareOrderingWithExplicitPriorityAndConnection(): vo
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
$this->assertMiddlewareInjected($container, 'conn2', PHP7Middleware::class);
$this->assertMiddlewareNotInjected($container, 'conn2', ConnectionAwarePHP7Middleware::class);
- $this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class]);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class];
+ $this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
}
public function testAddMiddlewareOrderingWithExplicitPriorityPerConnection(): void
@@ -252,8 +258,10 @@ public function testAddMiddlewareOrderingWithExplicitPriorityPerConnection(): vo
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
$this->assertMiddlewareInjected($container, 'conn2', PHP7Middleware::class);
$this->assertMiddlewareInjected($container, 'conn2', ConnectionAwarePHP7Middleware::class, true);
- $this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class]);
- $this->assertMiddlewareOrdering($container, 'conn2', [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class]);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class];
+ $this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, PHP7Middleware::class, ConnectionAwarePHP7Middleware::class] : [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class];
+ $this->assertMiddlewareOrdering($container, 'conn2', $expectedMiddlewares);
}
public function testAddMiddlewareOrderingWithInheritedPriorityPerConnection(): void
@@ -292,8 +300,10 @@ public function testAddMiddlewareOrderingWithInheritedPriorityPerConnection(): v
$this->assertMiddlewareInjected($container, 'conn2', PHP7Middleware::class);
$this->assertMiddlewareNotInjected($container, 'conn2', ConnectionAwarePHP7Middleware::class);
$this->assertMiddlewareInjected($container, 'conn2', 'some_middleware_class');
- $this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, 'some_middleware_class', PHP7Middleware::class]);
- $this->assertMiddlewareOrdering($container, 'conn2', [PHP7Middleware::class, 'some_middleware_class']);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, 'some_middleware_class', PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, 'some_middleware_class', PHP7Middleware::class];
+ $this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
+ $expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, PHP7Middleware::class, 'some_middleware_class'] : [PHP7Middleware::class, 'some_middleware_class'];
+ $this->assertMiddlewareOrdering($container, 'conn2', $expectedMiddlewares);
}
/** @requires PHP 8 */
@@ -327,15 +337,28 @@ public function testAddMiddlewareOrderingWithAttributeForAutoconfiguration(): vo
$this->assertMiddlewareInjected($container, 'conn2', AutoconfiguredMiddleware::class);
$this->assertMiddlewareInjected($container, 'conn2', AutoconfiguredMiddlewareWithConnection::class);
$this->assertMiddlewareInjected($container, 'conn2', AutoconfiguredMiddlewareWithPriority::class);
- $this->assertMiddlewareOrdering($container, 'conn1', [
+ $expectedMiddlewares = class_exists(Listener::class) ? [
+ IdleConnectionMiddleware::class,
AutoconfiguredMiddlewareWithPriority::class,
AutoconfiguredMiddleware::class,
- ]);
- $this->assertMiddlewareOrdering($container, 'conn2', [
+ ] :
+ [
+ AutoconfiguredMiddlewareWithPriority::class,
+ AutoconfiguredMiddleware::class,
+ ];
+ $this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
+ $expectedMiddlewares = class_exists(Listener::class) ? [
+ IdleConnectionMiddleware::class,
AutoconfiguredMiddlewareWithPriority::class,
AutoconfiguredMiddleware::class,
AutoconfiguredMiddlewareWithConnection::class,
- ]);
+ ] :
+ [
+ AutoconfiguredMiddlewareWithPriority::class,
+ AutoconfiguredMiddleware::class,
+ AutoconfiguredMiddlewareWithConnection::class,
+ ];
+ $this->assertMiddlewareOrdering($container, 'conn2', $expectedMiddlewares);
}
private function createContainer(callable $func, bool $addConnections = true): ContainerBuilder
diff --git a/tests/DependencyInjection/DoctrineExtensionTest.php b/tests/DependencyInjection/DoctrineExtensionTest.php
index 1468a65d..904b5ad3 100644
--- a/tests/DependencyInjection/DoctrineExtensionTest.php
+++ b/tests/DependencyInjection/DoctrineExtensionTest.php
@@ -1424,6 +1424,57 @@ public function testDefinitionsToLogQueriesLoggingFalse(): void
$this->assertArrayNotHasKey('doctrine.middleware', $abstractMiddlewareDefTags);
}
+ /** @requires function Symfony\Bridge\Doctrine\Middleware\IdleConnection\Driver::__construct */
+ public function testDefinitionsIdleConnection(): void
+ {
+ $container = $this->getContainer();
+ $extension = new DoctrineExtension();
+
+ $config = BundleConfigurationBuilder::createBuilder()
+ ->addConnection([
+ 'connections' => [
+ 'conn1' => [
+ 'password' => 'foo',
+ 'logging' => false,
+ 'profiling' => false,
+ 'idle_connection_ttl' => 15,
+ ],
+ 'conn2' => [
+ 'password' => 'bar',
+ 'logging' => false,
+ 'profiling' => true,
+ ],
+ ],
+ ])
+ ->build();
+
+ $extension->load([$config], $container);
+
+ $this->assertTrue($container->hasDefinition('doctrine.dbal.idle_connection_middleware'));
+
+ $abstractMiddlewareDef = $container->getDefinition('doctrine.dbal.idle_connection_middleware');
+ $ttlByConnection = $abstractMiddlewareDef->getArgument(1);
+
+ $this->assertArrayHasKey('conn1', $ttlByConnection);
+ $this->assertEquals(15, $ttlByConnection['conn1']);
+ $this->assertArrayHasKey('conn2', $ttlByConnection);
+ $this->assertEquals(600, $ttlByConnection['conn2']);
+
+ $abstractMiddlewareDefTags = $container->getDefinition('doctrine.dbal.idle_connection_middleware')->getTags();
+
+ $idleConnectionMiddlewareTagAttributes = [];
+ foreach ($abstractMiddlewareDefTags as $tag => $attributes) {
+ if ($tag !== 'doctrine.middleware') {
+ continue;
+ }
+
+ $idleConnectionMiddlewareTagAttributes = $attributes;
+ }
+
+ $this->assertTrue(in_array(['connection' => 'conn1', 'priority' => 10], $idleConnectionMiddlewareTagAttributes, true), 'Tag with connection conn1 not found for doctrine.dbal.idle_connection_middleware');
+ $this->assertTrue(in_array(['connection' => 'conn2', 'priority' => 10], $idleConnectionMiddlewareTagAttributes, true), 'Tag with connection conn2 found for doctrine.dbal.idle_connection_middleware');
+ }
+
/**
* @requires function \Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver::__construct
* @testWith [true]
diff --git a/tests/Middleware/IdleConnectionMiddlewareTest.php b/tests/Middleware/IdleConnectionMiddlewareTest.php
new file mode 100644
index 00000000..40527a95
--- /dev/null
+++ b/tests/Middleware/IdleConnectionMiddlewareTest.php
@@ -0,0 +1,29 @@
+ time() - 30, 'connectiontwo' => time() + 40]);
+ $ttlByConnection = ['connectionone' => 25, 'connectiontwo' => 60];
+
+ $middleware = new IdleConnectionMiddleware($connectionExpiries, $ttlByConnection);
+ $middleware->setConnectionName('connectionone');
+
+ $driverMock = $this->createMock(Driver::class);
+ $wrappedDriver = $middleware->wrap($driverMock);
+
+ $this->assertInstanceOf(IdleConnectionDriver::class, $wrappedDriver);
+ }
+}