Skip to content

Commit

Permalink
Allow userland middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
l-vo committed Feb 20, 2022
1 parent 543ec0f commit 5bd9712
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 76 deletions.
15 changes: 15 additions & 0 deletions Attribute/AsMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class AsMiddleware
{
/** @param string[] $connections */
public function __construct(
public array $connections = [],
) {
}
}
58 changes: 58 additions & 0 deletions DependencyInjection/Compiler/MiddlewaresPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;

use Doctrine\Bundle\DoctrineBundle\Middleware\ConnectionNameAwareInterface;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

use function array_keys;
use function array_search;
use function is_subclass_of;
use function sprintf;

final class MiddlewaresPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$middlewareAbstractDefs = [];
$middlewareConnections = [];
foreach ($container->findTaggedServiceIds('doctrine.middleware') as $id => $tags) {
$middlewareAbstractDefs[$id] = $container->getDefinition($id);
// When a def has doctrine.middleware tags with connection attributes equal to connection names
// registration of this middleware is limited to the connections with these names
foreach ($tags as $tag) {
if (! isset($tag['connection'])) {
continue;
}

$middlewareConnections[$id][] = $tag['connection'];
}
}

foreach (array_keys($container->getParameter('doctrine.connections')) as $name) {
$middlewareDefs = [];
foreach ($middlewareAbstractDefs as $id => $abstractDef) {
if (isset($middlewareConnections[$id]) && ! array_search($name, $middlewareConnections[$id], true)) {
continue;
}

$middlewareDefs[] = $childDef = $container->setDefinition(
sprintf('%s.%s', $id, $name),
new ChildDefinition($id)
);

if (! is_subclass_of($abstractDef->getClass(), ConnectionNameAwareInterface::class)) {
continue;
}

$childDef->addMethodCall('setConnectionName', [$name]);
}

$container
->getDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name))
->addMethodCall('setMiddlewares', [$middlewareDefs]);
}
}
}
51 changes: 38 additions & 13 deletions DependencyInjection/DoctrineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsMiddleware;
use Doctrine\Bundle\DoctrineBundle\CacheWarmer\DoctrineMetadataCacheWarmer;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\ImportDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider;
Expand All @@ -13,8 +14,9 @@
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
use Doctrine\DBAL\Logging\LoggerChain;
use Doctrine\DBAL\Logging\Middleware;
use Doctrine\DBAL\Sharding\PoolingShardConnection;
use Doctrine\DBAL\Sharding\PoolingShardManager;
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
Expand Down Expand Up @@ -44,7 +46,6 @@
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
Expand All @@ -65,6 +66,8 @@
use function sprintf;
use function str_replace;

use const PHP_VERSION_ID;

/**
* DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
*/
Expand Down Expand Up @@ -143,9 +146,33 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
$container->setParameter('doctrine.connections', $connections);
$container->setParameter('doctrine.default_connection', $this->defaultConnection);

$connWithLogging = [];
foreach ($config['connections'] as $name => $connection) {
if ($connection['logging']) {
$connWithLogging[] = $name;
}

$this->loadDbalConnection($name, $connection, $container);
}

/** @psalm-suppress UndefinedClass */
$container->registerForAutoconfiguration(MiddlewareInterface::class)->addTag('doctrine.middleware');

if (PHP_VERSION_ID >= 80000 && method_exists(ContainerBuilder::class, 'registerAttributeForAutoconfiguration')) {
$container->registerAttributeForAutoconfiguration(AsMiddleware::class, static function (ChildDefinition $definition, AsMiddleware $attribute) {
if ($attribute->connections === []) {
$definition->addTag('doctrine.middleware');

return;
}

foreach ($attribute->connections as $connName) {
$definition->addTag('doctrine.middleware', ['connection' => $connName]);
}
});
}

$this->useMiddlewaresIfAvailable($container, $connWithLogging);
}

/**
Expand All @@ -160,7 +187,6 @@ protected function loadDbalConnection($name, array $connection, ContainerBuilder
$configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration'));
$logger = null;
if ($connection['logging']) {
$this->useMiddlewaresIfAvailable($connection, $container, $name, $configuration);
$logger = new Reference('doctrine.dbal.logger');
}

Expand Down Expand Up @@ -1073,25 +1099,24 @@ private function createArrayAdapterCachePool(ContainerBuilder $container, string
return $id;
}

/** @param array<string, mixed> $connection */
protected function useMiddlewaresIfAvailable(array $connection, ContainerBuilder $container, string $name, Definition $configuration): void
/** @param string[] $connWithLogging */
private function useMiddlewaresIfAvailable(ContainerBuilder $container, array $connWithLogging): void
{
/** @psalm-suppress UndefinedClass */
if (! interface_exists(Middleware::class)) {
if (! class_exists(Middleware::class)) {
return;
}

$container
->getDefinition('doctrine.dbal.logger')
->replaceArgument(0, null);

$loggingMiddlewareDef = $container->setDefinition(
sprintf('doctrine.dbal.%s_connection.logging_middleware', $name),
new ChildDefinition('doctrine.dbal.logging_middleware')
);
$loggingMiddlewareDef->addArgument(new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE));
$loggingMiddlewareDef->addTag('monolog.logger', ['channel' => 'doctrine']);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('middlewares.xml');

$configuration->addMethodCall('setMiddlewares', [[$loggingMiddlewareDef]]);
$loggingMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.logging_middleware');
foreach ($connWithLogging as $connName) {
$loggingMiddlewareAbstractDef->addTag('doctrine.middleware', ['connection' => $connName]);
}
}
}
8 changes: 8 additions & 0 deletions DoctrineBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\MiddlewaresPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\RemoveProfilerControllerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Proxy\Autoloader;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\DoctrineValidationPass;
Expand All @@ -26,6 +28,7 @@
use function assert;
use function class_exists;
use function clearstatcache;
use function interface_exists;
use function spl_autoload_unregister;

class DoctrineBundle extends Bundle
Expand Down Expand Up @@ -60,6 +63,11 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new CacheSchemaSubscriberPass(), PassConfig::TYPE_BEFORE_REMOVING, -10);
$container->addCompilerPass(new RemoveProfilerControllerPass());

/** @psalm-suppress UndefinedClass */
if (interface_exists(Middleware::class)) {
$container->addCompilerPass(new MiddlewaresPass());
}

if (! class_exists(RegisterUidTypePass::class)) {
return;
}
Expand Down
8 changes: 8 additions & 0 deletions Middleware/ConnectionNameAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Middleware;

interface ConnectionNameAwareInterface
{
public function setConnectionName(string $name): void;
}
3 changes: 0 additions & 3 deletions Resources/config/dbal.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@
<argument type="service" id="debug.stopwatch" on-invalid="null" />
</service>

<service id="doctrine.dbal.logging_middleware" class="Doctrine\DBAL\Logging\Middleware" abstract="true">
</service>

<service id="data_collector.doctrine" class="%doctrine.data_collector.class%" public="false">
<tag name="data_collector" template="@Doctrine/Collector/db.html.twig" id="db" priority="250" />
<argument type="service" id="doctrine" />
Expand Down
14 changes: 14 additions & 0 deletions Resources/config/middlewares.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="doctrine.dbal.logging_middleware" class="Doctrine\DBAL\Logging\Middleware" abstract="true">
<argument type="service" id="logger" on-invalid="null" />
<tag name="monolog.logger" channel="doctrine" />
<tag name="doctrine.middleware" />
</service>
</services>
</container>
Loading

0 comments on commit 5bd9712

Please sign in to comment.