Yet another web developer blog

Workaround to make a Drupal module compatible with D9 and D10 together

The Drupal 10 release, as described, is mostly about deprecations and removing old code. So it's pretty easy to port your modules to Drupal 10, they said!

But actually - not!

Together with deprecations, we have some libraries updated to the new major versions too, and this can bring you little unexpected surprises when you try to make your module compatible with both Drupal 9 and 10 versions together.

For example, if your module implements Psr\Log\LoggerInterface, you will got different implementations of the LoggerInterface::log() function, that can't be implemented in a way, compatible with both Drupal 9 and 10.

Here what we have in Drupal 9 interface:

    public function log($level, $message, array $context = array());

And here - in Drupal 10 interface:

    public function log($level, string|\Stringable $message, array $context = []): void;

So, we can't make our custom function log(), that implements the LoggerInterface, compatible with both versions, because in one version the $message argument type should be "mixed", in another - union of types.

But don't get frustrated and drop Drupal support immediately!

I've invented a pretty ugly, but working workaround for this problem - here it is:

use Psr\Log\LoggerInterface;

// A workaround to make the logger compatible with Drupal 9.x and 10.x together.
if (version_compare(\Drupal::VERSION, '10.0.0') <= 0) {
  require_once __DIR__ . '/OpenTelemetryLoggerProxyTrait.D9.inc';
}
else {
  require_once __DIR__ . '/OpenTelemetryLoggerProxyTrait.D10.inc';
}

/**
 * A custom logger shim to catch an suppress repeating errors.
 */
class OpenTelemetryLoggerProxy implements LoggerInterface {
  use RfcLoggerTrait;
  use OpenTelemetryLoggerProxyTrait;
...
  public function doLog($level, $message, array $context = []): void {

And the two traits, each for the corresponding Drupal version - here is for D9:

trait OpenTelemetryLoggerProxyTrait {

  public function log($level, $message, array $context = []): void {
    $this->doLog($level, $message, $context);
  }

  abstract public function doLog($level, $message, array $context = []);

}

And - for D10:

trait OpenTelemetryLoggerProxyTrait {

  public function log($level, string|\Stringable $message, array $context = []): void {
    $this->doLog($level, $message, $context);
  }

  abstract public function doLog($level, $message, array $context = []);

}

That's it!

And I deliberately made the extension .inc in the trait file names, not something that ends with .php, to exclude these files from code check scripts, which makes the whole workaround works well even in PHP 7.4 version!

You can see this example in action in my Drupal module OpenTelemetry.

Tags