Skip to content

Commit

Permalink
Add a priority attribute on doctrine.middleware tag and `#[AsMidd…
Browse files Browse the repository at this point in the history
…leware]` attribute (#1676)
  • Loading branch information
Okhoshi authored Jun 15, 2023
1 parent b2ec6c2 commit 8d983bb
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 13 deletions.
1 change: 1 addition & 0 deletions Attribute/AsMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class AsMiddleware
/** @param string[] $connections */
public function __construct(
public array $connections = [],
public ?int $priority = null,
) {
}
}
38 changes: 31 additions & 7 deletions DependencyInjection/Compiler/MiddlewaresPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

use function array_key_exists;
use function array_keys;
use function in_array;
use function array_map;
use function array_values;
use function is_subclass_of;
use function sprintf;
use function uasort;

final class MiddlewaresPass implements CompilerPassInterface
{
Expand All @@ -22,30 +25,39 @@ public function process(ContainerBuilder $container): void

$middlewareAbstractDefs = [];
$middlewareConnections = [];
$middlewarePriorities = [];
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'])) {
if (isset($tag['priority']) && ! isset($middlewarePriorities[$id])) {
$middlewarePriorities[$id] = $tag['priority'];
}

continue;
}

$middlewareConnections[$id][] = $tag['connection'];
$middlewareConnections[$id][$tag['connection']] = $tag['priority'] ?? null;
}
}

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

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

if (! is_subclass_of($abstractDef->getClass(), ConnectionNameAwareInterface::class)) {
continue;
Expand All @@ -54,6 +66,18 @@ public function process(ContainerBuilder $container): void
$childDef->addMethodCall('setConnectionName', [$name]);
}

$middlewareDefs = array_map(
static fn ($id, $def) => [
$middlewareConnections[$id][$name] ?? $middlewarePriorities[$id] ?? 0,
$def[1],
$def[0],
],
array_keys($middlewareDefs),
array_values($middlewareDefs),
);
uasort($middlewareDefs, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]);
$middlewareDefs = array_map(static fn ($value) => $value[2], $middlewareDefs);

$container
->getDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name))
->addMethodCall('setMiddlewares', [$middlewareDefs]);
Expand Down
6 changes: 4 additions & 2 deletions DependencyInjection/DoctrineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,16 @@ protected function dbalLoad(array $config, ContainerBuilder $container)
$container->registerForAutoconfiguration(MiddlewareInterface::class)->addTag('doctrine.middleware');

$container->registerAttributeForAutoconfiguration(AsMiddleware::class, static function (ChildDefinition $definition, AsMiddleware $attribute) {
$priority = isset($attribute->priority) ? ['priority' => $attribute->priority] : [];

if ($attribute->connections === []) {
$definition->addTag('doctrine.middleware');
$definition->addTag('doctrine.middleware', $priority);

return;
}

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

Expand Down
35 changes: 33 additions & 2 deletions Resources/doc/middlewares.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,45 @@ Let's limit our middleware to a connection named ``legacy``:
}
}
If you register multiple middlewares in your application, they will be executed
in the order they were registered. If some middleware needs to be executed
before another, you can set priority through the ``AsMiddleware`` PHP attribute.
This priority can be any integer, positive or negative. The higher the priority,
the earlier the middleware is executed. If no priority is defined, the priority
is considered 0. Let's make sure our middleware is the first middleware
executed, so that we don't set up debugging or logging if the connection will
be prevented:

.. code-block:: php
<?php
namespace App\Middleware;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsMiddleware;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Middleware;
#[AsMiddleware(priority: 10)]
class PreventRootConnectionMiddleware implements Middleware
{
public function wrap(Driver $driver): Driver
{
return new PreventRootConnectionDriver($driver);
}
}
``priority`` and ``connections`` can be used together to restrict a middleware
to a specific connection while changing its priority.

All the examples presented above assume ``autoconfigure`` is enabled.
If ``autoconfigure`` is disabled, the ``doctrine.middleware`` tag must be
added to the middleware. This tag supports a ``connections`` attribute to
limit the scope of the middleware.
limit the scope of the middleware and a ``priority`` attribute to change
the execution order of the registered middlewares.

.. note::

Middlewares have been introduced in version 3.2 of ``doctrine/dbal``
and at least the 2.6 version of ``doctrine/doctrine-bundle`` is needed
to integrate them in Symfony as shown above.

Loading

0 comments on commit 8d983bb

Please sign in to comment.