Service Providers in the Real World

What are Service Providers?

Service providers offer an API that allows for altering existing services, and adding new services to the container dynamically.  They are a bit atypical as far as services go, but are pretty simple overall.

I could go into excessive detail about service providers here, but that's what the documentation is for, right?

When to use service providers?

The documentation is great for describing what and how, but sadly, and perhaps most importantly, the when and why is nowhere to be seen.  This post describes one compelling use case for a service provider that solves an unavoidable real-world problem.

When backwards compatibility promises are broken

Drupal 10.1 introduced a major refactoring of the asset aggregation system.  Although these changes didn't technically change any API signatures, the changes did change how this system works on fundamental level.  This change introduced fatal errors into the Inline All CSS contributed module.  This breakage happened on a minor core release 😱; after semantic versioning support for the entire Drupal 10 application life-cycle had been declared 😱😱😱.

So now I'm on the hook for the promise that I made with declaring such support, even in face of a very much unexpected upstream breakage.  Enter Service Providers.

Drupal 10.1 switched out the asset.css.collection_optimizer service with a brand new implementation that attempts to parallelize asset aggregation across multiple HTTP requests.  In theory, these requests can be load balanced horizontally across the infrastructure, but in practice, I'm unsure of the real world benefits of this decision. Regardless, because of API backwards compatibility guarantees, the old service class has to remain part of Drupal until 11.0.

Using a Service Provider, I was able to restore the old CSS collection optimizer service that was replaced in Drupal version 10.1.  This marks what I feel is a valid real-world use case for a Service Provider.  Here's the full addition to the contributed module:

<?php

namespace Drupal\inline_all_css;

use Drupal\Core\Asset\CssCollectionOptimizer;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;

/**
 * A service provider implementation added for Drupal 10.1+.
 *
 * @todo Figure something else out in https://www.drupal.org/i/3394740
 */
class InlineAllCssServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    if (version_compare(\Drupal::VERSION, '10.1', '>=')) {
      if ($container->hasDefinition('asset.css.collection_optimizer')) {
        $definition = $container->getDefinition('asset.css.collection_optimizer');
        $definition->setClass(CssCollectionOptimizer::class);
        $definition->setArguments([
          new Reference('asset.css.collection_grouper'),
          new Reference('asset.css.optimizer'),
          new Reference('asset.css.dumper'),
          new Reference('state'),
          new Reference('file_system'),
        ]);
      }
    }
  }

Acknowledgements

Thanks to Conrad Lara for the tip about using a service provider to provide a solution throughout the Drupal 10 life-cycle.