 [ ← blog / drupal ](/) May 30, 2023  ·  \#drupal  ·  4 min read 

# Factories and dependency injection



Last week I wrote about [dependency injection anti-patterns in Drupal](https://mglaman.dev/blog/dependency-injection-anti-patterns-drupal). These anti-patterns occur when your service's constructor has logic that interacts with the injected dependent services beyond assigning them to properties. These anti-patterns include creating a new object from a factory or retrieving an object from a stack. However, Symfony's service container supports defining services built from factories. This can streamline your code and follow best practices when using dependency injection. Drupal uses this factory service design pattern for cache bins, loggers, and the HTTP client service.

## Factories for services

Factories provide a way to create objects that are not services. Consider them a way to abstract away dependency injection in complex objects to that your service does not need to. Symfony supports using the [factory design pattern with dependency injection](https://symfony.com/doc/current/service_container/factories.html). The service defines the factory (class or service ID) and method for object creation. Then the service container will invoke that factory method and return the created object.

Here is the service definition for the `default` cache bin service:

```yml
  cache.default:
    class: Drupal\Core\Cache\CacheBackendInterface
    tags:
      - { name: cache.bin }
    factory: ['@cache_factory', 'get']
    arguments: [default]
```

This specifies that the class will be an instance of `Drupal\Core\Cache\CacheBackendInterface`. The service container will invoke the `get` method on the `@cache_factory` service. The `arguments` values are passed as arguments to the `get` method. The `@cache_factory` service is backed by the `Drupal\Core\Cache\CacheFactory` class. The service container will call `CacheFactory::get('default')`. This factory reads from settings overrides and the `cache_default_bin_backends` service container parameter [to determine what kind of backend class to create](https://git.drupalcode.org/project/drupal/-/blob/11.x/core/lib/Drupal/Core/Cache/CacheFactory.php#L65-83).

Loggers follow the same approach.

```yml
  logger.channel_base:
    abstract: true
    class: Drupal\Core\Logger\LoggerChannel
    factory: ['@logger.factory', 'get']
    
  logger.channel.default:
    parent: logger.channel_base
    arguments: ['system']
```

The `logger.channel.default` gets constructed by the service container with `LoggerChannelFactory::get('system')`. Drupal provides the abstract service definition `logger.channel_base` to simplify defining additional logger channels. This factory sets up logger channels and injects the actual logging services – such as the database logger or syslog.

The HTTP client is created from a factory, as well.

```yml
  http_client:
    class: GuzzleHttp\Client
    factory: ['@http_client_factory', 'fromOptions']
```

This factory ensures HTTP client middlewares are configured on the Guzzle client, along with any settings overrides.

## Factory injection pattern with configuration objects

What if we used the factory pattern with dependency injection using configuration objects? I honestly never considered this. [Ross commented](https://mglaman.dev/blog/dependency-injection-anti-patterns-drupal#comment-6193215474) about their approach on my blog post:

```yml
services:
  my_module.config:
  class: Drupal\Core\Config\ImmutableConfig
  factory: config.factory:get
  arguments: ['my_module.settings']
```

This does streamline handling configuration objects. Instead of injecting the configuration factory and having to call `$this->configFactory->get('my_module.settings')` in your logic, you have the configuration object as a property while not breaking design patterns. But is it a good idea? What are the side effects? One of my concerns around anti-patterns in dependency injection is unintended side effects when the service is constructed. Fetching configuration through a factory service definition raises a few flags purely due to how the configuration factory works. The factories for cache bins, loggers, and the HTTP client service are fairly light. The configuration factory is more of a repository than a factory. And that *little* nuance is what makes me hesitant.

The flow for loading configuration works something like this:

1. Check if the configuration object exists in the class's static cache (`\Drupal\Core\Config\ConfigFactory::$cache`), return that cached object if it is.
2. Read the configuration object from configuration storage. The first storage is `\Drupal\Core\Config\CachedStorage` which checks if the config object exists in the config `cache` bin. If the object isn't cached, then it is read from the active configuration store (the database.)
3. An `ImmutableConfig` object is created from the data retrieved from storage in `\Drupal\Core\Config\ConfigFactory::createConfigObject`.
4. Configuration overrides are applied to the config object – configuration overrides are the $`config['my_module.settings']` declarations in your `settings.php` file.
5. The configuration object is returned

My concern is that retrieving a configuration object touches cache and the database store. Those are two connections that may not be needed if your service is initialized but the logic touching the configuration object does not run. Although, I'd much rather see this approach than someone manually assigning a configuration object to a property. This way if the service is serialized, implementing `\Drupal\Core\DependencyInjection\DependencySerializationTrait` will properly re-initialize the configuration object if the service is serialized.

 

  ![](/themes/custom/mglaman/images/headshot.webp)Thanks for reading

I write about Drupal, PHP, and the quiet infrastructure behind large sites — caching, config, and testing. New posts land every week or two.

 [Subscribe via RSS →](/rss.xml) [About me →](/about-me) 

 



 

  [← Older

Dependency injection anti-patterns in Drupal

 ](/blog/dependency-injection-anti-patterns-drupal) [Newer →

Retrofit: Running legacy Drupal 7 code on your Drupal 10 site

 ](/blog/retrofit-running-legacy-drupal-7-code-your-drupal-10-site)