Doctrine DBAL driver middlewares

New in version 12.3

Introduction

Doctrine DBAL supports custom driver middlewares since version 3. These middlewares act as a decorator around the actual Driver component. Subsequently, the Connection, Statement and Result components can be decorated as well. These middlewares must implement the \Doctrine\DBAL\Driver\Middleware interface. A common use case would be a middleware to implement SQL logging capabilities.

For more information on driver middlewares, see the Architecture chapter of the Doctrine DBAL documentation. Furthermore, look up the implementation of the EXT:adminpanel/Classes/Log/DoctrineSqlLoggingMiddleware.php (GitHub) in the Admin Panel system extension as an example.

Global driver middlewares and driver middlewares for a specific connection are combined for a connection. They are sortable.

Register a global driver middleware

New in version 13.0

Global driver middlewares are applied to all configured connections.

Example:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Doctrine\Driver\CustomGlobalDriverMiddleware;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['my-ext/custom-global-driver-middleware'] = [
    'target' => CustomGlobalDriverMiddleware::class,
    'after' => [
        // NOTE: Custom driver middleware should be registered after essential
        //       TYPO3 Core driver middlewares. Use the following identifiers
        //       to ensure that.
        'typo3/core/custom-platform-driver-middleware',
        'typo3/core/custom-pdo-driver-result-middleware',
    ],
];
Copied!

Disable a global middleware for a specific connection

Example:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Doctrine\Driver\CustomGlobalDriverMiddleware;

defined('TYPO3') or die();

// Register a global middleware
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['my-ext/custom-global-driver-middleware'] = [
    'target' => CustomGlobalDriverMiddleware::class,
    'after' => [
        // NOTE: Custom driver middleware should be registered after essential
        //       TYPO3 Core driver middlewares. Use the following identifiers
        //       to ensure that.
        'typo3/core/custom-platform-driver-middleware',
        'typo3/core/custom-pdo-driver-result-middleware',
    ],
];

// Disable a global driver middleware for a specific connection
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['my-ext/custom-global-driver-middleware']['disabled'] = true;
Copied!

Register a driver middleware for a specific connection

New in version 12.3

Deprecated since version 13.0

In this example, the custom driver middleware MyDriverMiddleware is added to the Default connection:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Doctrine\Driver\MyDriverMiddleware;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares']['driver-middleware-identifier'] = [
    'target' => MyDriverMiddleware::class,
    'after' => [
        // NOTE: Custom driver middleware should be registered after essential
        //       TYPO3 Core driver middlewares. Use the following identifiers
        //       to ensure that.
        'typo3/core/custom-platform-driver-middleware',
        'typo3/core/custom-pdo-driver-result-middleware',
    ],
];
Copied!

Migration

For example:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares']['driver-middleware-identifier']
    = MyDriverMiddlewareClass::class;
Copied!

needs to be converted to:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Doctrine\Driver\MyDriverMiddleware;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares']['driver-middleware-identifier'] = [
    'target' => MyDriverMiddleware::class,
    'after' => [
        // NOTE: Custom driver middleware should be registered after essential
        //       TYPO3 Core driver middlewares. Use the following identifiers
        //       to ensure that.
        'typo3/core/custom-platform-driver-middleware',
        'typo3/core/custom-pdo-driver-result-middleware',
    ],
];
Copied!

Registration for driver middlewares for TYPO3 v12 and v13

Extension authors providing dual Core support in one extension version can use the Typo3Version class to provide the configuration suitable for the Core version and avoiding the deprecation notice:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Doctrine\Driver\MyDriverMiddleware;
use TYPO3\CMS\Core\Information\Typo3Version;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares']['driver-middleware-identifier']
    = ((new Typo3Version())->getMajorVersion() < 13)
    ? MyDriverMiddleware::class
    : [
        'target' => MyDriverMiddleware::class,
        'after' => [
            // NOTE: Custom driver middleware should be registered after essential
            //       TYPO3 Core driver middlewares. Use the following identifiers
            //       to ensure that.
            'typo3/core/custom-platform-driver-middleware',
            'typo3/core/custom-pdo-driver-result-middleware',
        ],
    ];
Copied!

Sorting of driver middlewares

New in version 13.0

Global driver middlewares and connection driver middlewares are combined for a connection.

TYPO3 makes the global and connection driver middlewares sortable similar to the PSR-15 middleware stack. The available structure for a middleware configuration is:

target

target
Data type

string

Required

yes

The fully-qualified class name of the driver middleware.

before

before
Data type

list of strings

Required

no

Default

[]

A list of middleware identifiers the current middleware should be registered before.

after

after
Data type

list of strings

Required

no

Default

[]

A list of middleware identifiers the current middleware should be registered after.

disabled

disabled
Data type

boolean

Required

no

Default

false

It can be used to disable a global middleware for a specific connection.

Example:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Doctrine\Driver\CustomGlobalDriverMiddleware;

defined('TYPO3') or die();

// Register global driver middleware
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['global-driver-middleware-identifier'] = [
    'target' => CustomGlobalDriverMiddleware::class,
    'disabled' => false,
    'after' => [
        // NOTE: Custom driver middleware should be registered after essential
        //       TYPO3 Core driver middlewares. Use the following identifiers
        //       to ensure that.
        'typo3/core/custom-platform-driver-middleware',
        'typo3/core/custom-pdo-driver-result-middleware',
    ],
    'before' => [
        'some-driver-middleware-identifier',
    ],
];

// Disable a global driver middleware for a connection
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['global-driver-middleware-identifier'] = [
    // To disable a global driver middleware, setting disabled to true for a
    // connection is enough. Repeating target, after and/or before configuration
    // is not required.
    'disabled' => true,
];
Copied!

The interface UsableForConnectionInterface

New in version 13.0

Doctrine DBAL driver middlewares can be registered globally for all connections or for specific connections. Due to the nature of the decorator pattern, it may become hard to determine for a specific configuration or drivers, if a middleware needs to be executed only for a subset, for example, only specific drivers.

TYPO3 provides a custom \TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface driver middleware interface which requires the implementation of the method canBeUsedForConnection():

interface UsableForConnectionInterface
Fully qualified name
\TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface

Custom driver middleware can implement this interface to decide per connection and connection configuration if it should be used or not. For example, registering a global driver middleware which only takes affect on connections using a specific driver like pdo_sqlite.

Usually this should be a rare case and mostly a driver middleware can be simply configured as a connection middleware directly, which leaves this more or less a special implementation detail for the TYPO3 core.

canBeUsedForConnection ( string $identifier, array $connectionParams)

Return true if the driver middleware should be used for the concrete connection.

param $identifier

the identifier

param $connectionParams

the connectionParams

Returns
bool

This allows to decide, if a middleware should be used for a specific connection, either based on the $connectionName or the $connectionParams, for example the concrete $connectionParams['driver'].

Example

The custom driver:

EXT:my_extension/Classes/DoctrineDBAL/CustomDriver.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\DoctrineDBAL;

use Doctrine\DBAL\Driver\Connection as DriverConnection;
// Using the abstract class minimize the methods to implement and therefore
// reduces a lot of boilerplate code. Override only methods that needed to be
// customized.
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;

final class CustomDriver extends AbstractDriverMiddleware
{
    public function connect(#[\SensitiveParameter] array $params): DriverConnection
    {
        $connection = parent::connect($params);

        // Do something custom on connect, for example wrapping the driver
        // connection class or executing some queries on connect.

        return $connection;
    }
}
Copied!

The custom driver middleware which implements the \TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface :

EXT:my_extension/Classes/DoctrineDBAL/CustomMiddleware.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\DoctrineDBAL;

use Doctrine\DBAL\Driver as DoctrineDriverInterface;
use Doctrine\DBAL\Driver\Middleware as DoctrineDriverMiddlewareInterface;
use MyVendor\MyExtension\DoctrineDBAL\CustomDriver as MyCustomDriver;
use TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface;

final class CustomMiddleware implements DoctrineDriverMiddlewareInterface, UsableForConnectionInterface
{
    public function wrap(DoctrineDriverInterface $driver): DoctrineDriverInterface
    {
        // Wrap the original or already wrapped driver with our custom driver
        // decoration class to provide additional features.
        return new MyCustomDriver($driver);
    }

    public function canBeUsedForConnection(
        string $identifier,
        array $connectionParams,
    ): bool {
        // Only use this driver middleware, if the configured connection driver
        // is 'pdo_sqlite' (sqlite using php-ext PDO).
        return ($connectionParams['driver'] ?? '') === 'pdo_sqlite';
    }
}
Copied!

Register the custom driver middleware:

EXT:my_extension/ext_localconf.php | config/system/additional.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\DoctrineDBAL\CustomMiddleware;

defined('TYPO3') or die();

// Register middleware globally, to include it for all connections which
// uses the 'pdo_sqlite' driver.
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['my-ext/custom-pdosqlite-driver-middleware'] = [
    'target' => CustomMiddleware::class,
    'after' => [
        // NOTE: Custom driver middleware should be registered after essential
        //       TYPO3 Core driver middlewares. Use the following identifiers
        //       to ensure that.
        'typo3/core/custom-platform-driver-middleware',
        'typo3/core/custom-pdo-driver-result-middleware',
    ],
];
Copied!