Skip to content

Commit 4bb9f20

Browse files
alli83ostrolucky
authored andcommitted
Register the idle connection listener
1 parent 5418e81 commit 4bb9f20

File tree

10 files changed

+195
-14
lines changed

10 files changed

+195
-14
lines changed

config/dbal.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@
101101
<tag name="controller.service_arguments" />
102102
</service>
103103

104+
<service id="doctrine.dbal.idle_connection_listener" class="Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener">
105+
<argument type="service" id="doctrine.dbal.connection_expiries" />
106+
<argument type="service" id="service_container" />
107+
<tag name="kernel.event_subscriber" />
108+
</service>
109+
104110
<service id="doctrine.dbal.default_schema_manager_factory" class="Doctrine\DBAL\Schema\DefaultSchemaManagerFactory" />
105111
<service id="doctrine.dbal.legacy_schema_manager_factory" class="Doctrine\DBAL\Schema\LegacySchemaManagerFactory" />
106112

config/middlewares.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8+
<service id="doctrine.dbal.connection_expiries" class="ArrayObject" />
89
<service id="doctrine.dbal.logging_middleware" class="Doctrine\DBAL\Logging\Middleware" abstract="true">
910
<argument type="service" id="logger" />
1011
<tag name="monolog.logger" channel="doctrine" />
@@ -17,5 +18,9 @@
1718
<argument type="service" id="doctrine.debug_data_holder" />
1819
<argument type="service" id="debug.stopwatch" on-invalid="null" />
1920
</service>
21+
<service id="doctrine.dbal.idle_connection_middleware" class="Doctrine\Bundle\DoctrineBundle\Middleware\IdleConnectionMiddleware" abstract="true">
22+
<argument type="service" id="doctrine.dbal.connection_expiries" />
23+
<argument /> <!-- check timing -->
24+
</service>
2025
</services>
2126
</container>

psalm.xml.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
<referencedClass name="Doctrine\ORM\ORMException"/>
4444
<!-- Dropped in DBAL 4 -->
4545
<referencedClass name="Doctrine\DBAL\Exception"/>
46+
<!-- Available starting from Symfony 7.1 -->
47+
<referencedClass name="Symfony\Bridge\Doctrine\Middleware\IdleConnection\Driver"/>
4648
</errorLevel>
4749
</UndefinedClass>
4850
<DuplicateClass>

src/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition
221221
->end()
222222
->booleanNode('disable_type_comments')->end()
223223
->scalarNode('server_version')->end()
224+
->integerNode('idle_connection_ttl')->defaultValue(600)->end()
224225
->scalarNode('driver_class')->end()
225226
->scalarNode('wrapper_class')->end()
226227
->booleanNode('keep_slave')

src/DependencyInjection/DoctrineExtension.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
3838
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
3939
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
40+
use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener;
4041
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
4142
use Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaListener;
4243
use Symfony\Bridge\Doctrine\SchemaListener\LockStoreSchemaListener;
@@ -83,7 +84,7 @@
8384
*
8485
* @final since 2.9
8586
* @psalm-type DBALConfig = array{
86-
* connections: array<string, array{logging: bool, profiling: bool, profiling_collect_backtrace: bool}>,
87+
* connections: array<string, array{logging: bool, profiling: bool, profiling_collect_backtrace: bool, idle_connection_ttl: int}>,
8788
* driver_schemes: array<string, string>,
8889
* default_connection: string,
8990
* types: array<string, string>,
@@ -196,6 +197,8 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
196197
$connWithLogging = [];
197198
$connWithProfiling = [];
198199
$connWithBacktrace = [];
200+
$ttlByConnection = [];
201+
199202
foreach ($config['connections'] as $name => $connection) {
200203
if ($connection['logging']) {
201204
$connWithLogging[] = $name;
@@ -209,6 +212,10 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
209212
}
210213
}
211214

215+
if ($connection['idle_connection_ttl'] > 0) {
216+
$ttlByConnection[$name] = $connection['idle_connection_ttl'];
217+
}
218+
212219
$this->loadDbalConnection($name, $connection, $container);
213220
}
214221

@@ -228,7 +235,16 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
228235
}
229236
});
230237

231-
$this->registerDbalMiddlewares($container, $connWithLogging, $connWithProfiling, $connWithBacktrace);
238+
$this->registerDbalMiddlewares($container, $connWithLogging, $connWithProfiling, $connWithBacktrace, array_keys($ttlByConnection));
239+
240+
$container->getDefinition('doctrine.dbal.idle_connection_middleware')->setArgument(1, $ttlByConnection);
241+
242+
if (class_exists(Listener::class)) {
243+
return;
244+
}
245+
246+
$container->removeDefinition('doctrine.dbal.idle_connection_listener');
247+
$container->removeDefinition('doctrine.dbal.idle_connection_middleware');
232248
}
233249

234250
/**
@@ -1186,12 +1202,14 @@ private function createArrayAdapterCachePool(ContainerBuilder $container, string
11861202
* @param string[] $connWithLogging
11871203
* @param string[] $connWithProfiling
11881204
* @param string[] $connWithBacktrace
1205+
* @param string[] $connWithTtl
11891206
*/
11901207
private function registerDbalMiddlewares(
11911208
ContainerBuilder $container,
11921209
array $connWithLogging,
11931210
array $connWithProfiling,
1194-
array $connWithBacktrace
1211+
array $connWithBacktrace,
1212+
array $connWithTtl
11951213
): void {
11961214
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
11971215
$loader->load('middlewares.xml');
@@ -1207,5 +1225,11 @@ private function registerDbalMiddlewares(
12071225
$debugMiddlewareAbstractDef
12081226
->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]);
12091227
}
1228+
1229+
$idleConnectionMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.idle_connection_middleware');
1230+
foreach ($connWithTtl as $connName) {
1231+
$idleConnectionMiddlewareAbstractDef
1232+
->addTag('doctrine.middleware', ['connection' => $connName, 'priority' => 10]);
1233+
}
12101234
}
12111235
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Doctrine\Bundle\DoctrineBundle\Middleware;
4+
5+
use ArrayObject;
6+
use Doctrine\DBAL\Driver;
7+
use Doctrine\DBAL\Driver\Middleware;
8+
use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Driver as IdleConnectionDriver;
9+
10+
class IdleConnectionMiddleware implements Middleware, ConnectionNameAwareInterface
11+
{
12+
private ArrayObject $connectionExpiries;
13+
/** @var array<string, int> */
14+
private array $ttlByConnection;
15+
private string $connectionName;
16+
17+
/**
18+
* @param ArrayObject<string, int> $connectionExpiries
19+
* @param array<string, int> $ttlByConnection
20+
*/
21+
public function __construct(ArrayObject $connectionExpiries, array $ttlByConnection)
22+
{
23+
$this->connectionExpiries = $connectionExpiries;
24+
$this->ttlByConnection = $ttlByConnection;
25+
}
26+
27+
public function setConnectionName(string $name): void
28+
{
29+
$this->connectionName = $name;
30+
}
31+
32+
public function wrap(Driver $driver): IdleConnectionDriver
33+
{
34+
return new IdleConnectionDriver($driver, $this->connectionExpiries, $this->ttlByConnection[$this->connectionName], $this->connectionName);
35+
}
36+
}

tests/DependencyInjection/AbstractDoctrineExtensionTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ public function testDbalLoadSinglePrimaryReplicaConnection(): void
220220
'host' => 'localhost',
221221
'unix_socket' => '/path/to/mysqld.sock',
222222
'driverOptions' => [PDO::ATTR_STRINGIFY_FETCHES => 1],
223+
'idle_connection_ttl' => 600,
223224
],
224225
$param['primary'],
225226
);
@@ -340,6 +341,7 @@ public function testLoadSimpleSingleConnection(): void
340341
'driver' => 'pdo_mysql',
341342
'driverOptions' => [],
342343
'defaultTableOptions' => [],
344+
'idle_connection_ttl' => 600,
343345
],
344346
new Reference('doctrine.dbal.default_connection.configuration'),
345347
method_exists(Connection::class, 'getEventManager')
@@ -379,6 +381,7 @@ public function testLoadSimpleSingleConnectionWithoutDbName(): void
379381
'driver' => 'pdo_mysql',
380382
'driverOptions' => [],
381383
'defaultTableOptions' => [],
384+
'idle_connection_ttl' => 600,
382385
],
383386
new Reference('doctrine.dbal.default_connection.configuration'),
384387
method_exists(Connection::class, 'getEventManager')
@@ -418,6 +421,7 @@ public function testLoadSingleConnection(): void
418421
'dbname' => 'sqlite_db',
419422
'memory' => true,
420423
'defaultTableOptions' => [],
424+
'idle_connection_ttl' => 600,
421425
],
422426
new Reference('doctrine.dbal.default_connection.configuration'),
423427
method_exists(Connection::class, 'getEventManager')

tests/DependencyInjection/Compiler/MiddlewarePassTest.php

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\MiddlewaresPass;
77
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension;
88
use Doctrine\Bundle\DoctrineBundle\Middleware\ConnectionNameAwareInterface;
9+
use Doctrine\Bundle\DoctrineBundle\Middleware\IdleConnectionMiddleware;
910
use Doctrine\DBAL\Driver;
1011
use Doctrine\DBAL\Driver\Middleware;
1112
use PHPUnit\Framework\TestCase;
1213
use Psr\Log\NullLogger;
14+
use Symfony\Bridge\Doctrine\Middleware\IdleConnection\Listener;
1315
use Symfony\Component\DependencyInjection\ContainerBuilder;
1416
use Symfony\Component\DependencyInjection\Definition;
1517
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
1618

1719
use function array_map;
20+
use function class_exists;
1821
use function implode;
1922
use function sprintf;
2023

@@ -170,7 +173,8 @@ public function testAddMiddlewareOrderingWithDefaultPriority(): void
170173

171174
$this->assertMiddlewareInjected($container, 'conn1', PHP7Middleware::class);
172175
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
173-
$this->assertMiddlewareOrdering($container, 'conn1', [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class]);
176+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, PHP7Middleware::class, ConnectionAwarePHP7Middleware::class] : [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class];
177+
$this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
174178
}
175179

176180
public function testAddMiddlewareOrderingWithExplicitPriority(): void
@@ -193,7 +197,8 @@ public function testAddMiddlewareOrderingWithExplicitPriority(): void
193197

194198
$this->assertMiddlewareInjected($container, 'conn1', PHP7Middleware::class);
195199
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
196-
$this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class]);
200+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class];
201+
$this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
197202
}
198203

199204
public function testAddMiddlewareOrderingWithExplicitPriorityAndConnection(): void
@@ -222,7 +227,8 @@ public function testAddMiddlewareOrderingWithExplicitPriorityAndConnection(): vo
222227
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
223228
$this->assertMiddlewareInjected($container, 'conn2', PHP7Middleware::class);
224229
$this->assertMiddlewareNotInjected($container, 'conn2', ConnectionAwarePHP7Middleware::class);
225-
$this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class]);
230+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class];
231+
$this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
226232
}
227233

228234
public function testAddMiddlewareOrderingWithExplicitPriorityPerConnection(): void
@@ -252,8 +258,10 @@ public function testAddMiddlewareOrderingWithExplicitPriorityPerConnection(): vo
252258
$this->assertMiddlewareInjected($container, 'conn1', ConnectionAwarePHP7Middleware::class, true);
253259
$this->assertMiddlewareInjected($container, 'conn2', PHP7Middleware::class);
254260
$this->assertMiddlewareInjected($container, 'conn2', ConnectionAwarePHP7Middleware::class, true);
255-
$this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class]);
256-
$this->assertMiddlewareOrdering($container, 'conn2', [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class]);
261+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, PHP7Middleware::class];
262+
$this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
263+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, PHP7Middleware::class, ConnectionAwarePHP7Middleware::class] : [PHP7Middleware::class, ConnectionAwarePHP7Middleware::class];
264+
$this->assertMiddlewareOrdering($container, 'conn2', $expectedMiddlewares);
257265
}
258266

259267
public function testAddMiddlewareOrderingWithInheritedPriorityPerConnection(): void
@@ -292,8 +300,10 @@ public function testAddMiddlewareOrderingWithInheritedPriorityPerConnection(): v
292300
$this->assertMiddlewareInjected($container, 'conn2', PHP7Middleware::class);
293301
$this->assertMiddlewareNotInjected($container, 'conn2', ConnectionAwarePHP7Middleware::class);
294302
$this->assertMiddlewareInjected($container, 'conn2', 'some_middleware_class');
295-
$this->assertMiddlewareOrdering($container, 'conn1', [ConnectionAwarePHP7Middleware::class, 'some_middleware_class', PHP7Middleware::class]);
296-
$this->assertMiddlewareOrdering($container, 'conn2', [PHP7Middleware::class, 'some_middleware_class']);
303+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, ConnectionAwarePHP7Middleware::class, 'some_middleware_class', PHP7Middleware::class] : [ConnectionAwarePHP7Middleware::class, 'some_middleware_class', PHP7Middleware::class];
304+
$this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
305+
$expectedMiddlewares = class_exists(Listener::class) ? [IdleConnectionMiddleware::class, PHP7Middleware::class, 'some_middleware_class'] : [PHP7Middleware::class, 'some_middleware_class'];
306+
$this->assertMiddlewareOrdering($container, 'conn2', $expectedMiddlewares);
297307
}
298308

299309
/** @requires PHP 8 */
@@ -327,15 +337,28 @@ public function testAddMiddlewareOrderingWithAttributeForAutoconfiguration(): vo
327337
$this->assertMiddlewareInjected($container, 'conn2', AutoconfiguredMiddleware::class);
328338
$this->assertMiddlewareInjected($container, 'conn2', AutoconfiguredMiddlewareWithConnection::class);
329339
$this->assertMiddlewareInjected($container, 'conn2', AutoconfiguredMiddlewareWithPriority::class);
330-
$this->assertMiddlewareOrdering($container, 'conn1', [
340+
$expectedMiddlewares = class_exists(Listener::class) ? [
341+
IdleConnectionMiddleware::class,
331342
AutoconfiguredMiddlewareWithPriority::class,
332343
AutoconfiguredMiddleware::class,
333-
]);
334-
$this->assertMiddlewareOrdering($container, 'conn2', [
344+
] :
345+
[
346+
AutoconfiguredMiddlewareWithPriority::class,
347+
AutoconfiguredMiddleware::class,
348+
];
349+
$this->assertMiddlewareOrdering($container, 'conn1', $expectedMiddlewares);
350+
$expectedMiddlewares = class_exists(Listener::class) ? [
351+
IdleConnectionMiddleware::class,
335352
AutoconfiguredMiddlewareWithPriority::class,
336353
AutoconfiguredMiddleware::class,
337354
AutoconfiguredMiddlewareWithConnection::class,
338-
]);
355+
] :
356+
[
357+
AutoconfiguredMiddlewareWithPriority::class,
358+
AutoconfiguredMiddleware::class,
359+
AutoconfiguredMiddlewareWithConnection::class,
360+
];
361+
$this->assertMiddlewareOrdering($container, 'conn2', $expectedMiddlewares);
339362
}
340363

341364
private function createContainer(callable $func, bool $addConnections = true): ContainerBuilder

tests/DependencyInjection/DoctrineExtensionTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,57 @@ public function testDefinitionsToLogQueriesLoggingFalse(): void
14241424
$this->assertArrayNotHasKey('doctrine.middleware', $abstractMiddlewareDefTags);
14251425
}
14261426

1427+
/** @requires function Symfony\Bridge\Doctrine\Middleware\IdleConnection\Driver::__construct */
1428+
public function testDefinitionsIdleConnection(): void
1429+
{
1430+
$container = $this->getContainer();
1431+
$extension = new DoctrineExtension();
1432+
1433+
$config = BundleConfigurationBuilder::createBuilder()
1434+
->addConnection([
1435+
'connections' => [
1436+
'conn1' => [
1437+
'password' => 'foo',
1438+
'logging' => false,
1439+
'profiling' => false,
1440+
'idle_connection_ttl' => 15,
1441+
],
1442+
'conn2' => [
1443+
'password' => 'bar',
1444+
'logging' => false,
1445+
'profiling' => true,
1446+
],
1447+
],
1448+
])
1449+
->build();
1450+
1451+
$extension->load([$config], $container);
1452+
1453+
$this->assertTrue($container->hasDefinition('doctrine.dbal.idle_connection_middleware'));
1454+
1455+
$abstractMiddlewareDef = $container->getDefinition('doctrine.dbal.idle_connection_middleware');
1456+
$ttlByConnection = $abstractMiddlewareDef->getArgument(1);
1457+
1458+
$this->assertArrayHasKey('conn1', $ttlByConnection);
1459+
$this->assertEquals(15, $ttlByConnection['conn1']);
1460+
$this->assertArrayHasKey('conn2', $ttlByConnection);
1461+
$this->assertEquals(600, $ttlByConnection['conn2']);
1462+
1463+
$abstractMiddlewareDefTags = $container->getDefinition('doctrine.dbal.idle_connection_middleware')->getTags();
1464+
1465+
$idleConnectionMiddlewareTagAttributes = [];
1466+
foreach ($abstractMiddlewareDefTags as $tag => $attributes) {
1467+
if ($tag !== 'doctrine.middleware') {
1468+
continue;
1469+
}
1470+
1471+
$idleConnectionMiddlewareTagAttributes = $attributes;
1472+
}
1473+
1474+
$this->assertTrue(in_array(['connection' => 'conn1', 'priority' => 10], $idleConnectionMiddlewareTagAttributes, true), 'Tag with connection conn1 not found for doctrine.dbal.idle_connection_middleware');
1475+
$this->assertTrue(in_array(['connection' => 'conn2', 'priority' => 10], $idleConnectionMiddlewareTagAttributes, true), 'Tag with connection conn2 found for doctrine.dbal.idle_connection_middleware');
1476+
}
1477+
14271478
/**
14281479
* @requires function \Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver::__construct
14291480
* @testWith [true]

0 commit comments

Comments
 (0)