Core Architecture - services.yml and Autowiring

The services.yml system is the backbone of Drupal’s dependency injection architecture. It defines how objects are created, how dependencies are wired together, and how services are shared across the application.

Autowiring builds on top of this system by allowing Drupal to automatically resolve dependencies based on PHP type hints. Together, services.yml and autowiring form the foundation of modern Drupal architecture.

The goal is to understand how services are registered and resolved, and how Drupal balances explicit configuration with automation.

Why services.yml Belongs in Core Architecture

services.yml is not just configuration. It defines the object graph of a Drupal application.

Drupal core relies on services.yml to:

  • Register core and contributed services
  • Control object lifecycles
  • Inject dependencies
  • Tag and organize services
  • Assemble the middleware and event systems

Understanding services.yml is essential for understanding how Drupal boots and runs.

What Is services.yml

A services.yml file declares services and their metadata.

Key characteristics:

  • YAML based
  • Loaded during container compilation
  • Converted into a compiled PHP container
  • Cached for performance

Every module can define its own services.yml file.

Basic Service Definition

Example: my_module.services.yml

services:
  my_module.example_service:
    class: Drupal\my_module\Service\ExampleService

This registers a service with a unique service ID and a PHP class.

Injecting Dependencies Explicitly

Dependencies can be declared explicitly using arguments. This is the most precise and predictable way to define services, especially when scalar values or non-default services are required.

Dependencies can be declared using arguments.

services:
  my_module.example_service:
    class: Drupal\my_module\Service\ExampleService
    arguments:
      - '@entity_type.manager'
      - '@logger.channel.default'

The @ symbol tells Drupal to fetch another service from the container.

Service IDs vs Classes

This distinction becomes especially important when working with autowiring.

  • The service ID is how Drupal identifies a service internally
  • The class is the PHP implementation

Autowiring resolves dependencies based on type hints, not service IDs. When multiple services share the same class or interface, aliases or explicit configuration are required.

Important distinction:

  • Service ID is how Drupal references a service
  • Class is the PHP implementation

Multiple service IDs can reference the same class with different arguments.

What Is Autowiring

Autowiring allows Drupal to resolve constructor arguments automatically using type hints.

Instead of listing arguments explicitly, Drupal inspects the constructor signature and injects matching services.

Autowiring reduces boilerplate but does not replace services.yml.

Enabling Autowiring

Autowiring must be enabled explicitly.

services:
  my_module.example_service:
    class: Drupal\my_module\Service\ExampleService
    autowire: true

Drupal will now resolve constructor dependencies automatically.

Autowiring Example

Service class:

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Psr\Log\LoggerInterface;

class ExampleService {

  public function __construct(
    EntityTypeManagerInterface $entityTypeManager,
    LoggerInterface $logger
  ) {}
}

With autowiring enabled, no arguments need to be defined.

Autowiring Rules and Limitations

Autowiring works when:

  • Constructor arguments are type hinted
  • The type matches a known service or alias
  • Interfaces resolve unambiguously to a single service

Autowiring becomes ambiguous when:

  • Multiple services share the same class or interface
  • Scalar values or configuration are required

In such cases, Drupal resolves ambiguity using service aliases or, in Drupal 10.1+, the #[Autowire] attribute to explicitly select the desired service. When ambiguity remains, explicit arguments in services.yml are required.

Autowiring works when:

  • Constructor arguments are type hinted
  • The type matches a known service
  • Interfaces map to services

Autowiring does not work well when:

  • Multiple services share the same class or interface
  • Scalar values are required
  • Configuration values are needed

In these cases, explicit arguments are required.

Service Tags

Tags attach behavior or classification to services.

Examples:

  • event_subscriber
  • http_middleware
  • access_check
  • twig.extension

Tags are critical for discovery based systems.

tags:
  - { name: event_subscriber }

Public vs Private Services

By default, services are private.

Key points:

  • Private services cannot be fetched directly
  • Public services can be fetched using the container
public: true

Drupal encourages private services and dependency injection.

Container Compilation

During cache rebuild:

  1. All services.yml files are loaded
  2. Service definitions are merged
  3. Compiler passes modify or optimize definitions
  4. The container is compiled into PHP

Compiler passes are low-level mechanisms that can alter service definitions programmatically before the container is finalized. They are used extensively by Drupal core and advanced contributed modules and will be covered in later architecture articles.

During cache rebuild:

  1. All services.yml files are loaded
  2. Service definitions are merged
  3. Compiler passes are executed
  4. The container is compiled into PHP

The compiled container is cached and reused.

services.yml vs Annotations vs YAML

Different systems use different registration mechanisms:

  • services.yml registers services
  • Annotations register plugins
  • routing.yml registers routes

Each has a distinct architectural purpose.

Common Mistakes

  • Overusing autowiring
  • Relying on \Drupal::service()
  • Making services public unnecessarily
  • Injecting unused services
  • Mixing configuration and logic

services.yml should remain explicit and readable.

Drupal 10 and 11 Best Practices

  • Use explicit arguments for clarity
  • Use autowiring selectively
  • Prefer interfaces over concrete classes
  • Keep services small and focused
  • Avoid service locators

How services.yml Fits with Other Core Systems

services.yml integrates with:

  • Service container
  • Event system
  • Middleware stack
  • Access checks
  • Plugin managers

Nearly every core system depends on service definitions.

Acquia Exam Notes and Cheat Sheet

Key points to remember:

  • services.yml defines the container
  • Autowiring uses type hints
  • Tags enable discovery
  • Services are private by default
  • Container is compiled and cached

Common exam traps:

  • Assuming autowiring replaces services.yml
  • Confusing service IDs with class names
  • Forgetting to rebuild cache after changes
  • Using public services unnecessarily

Quick decision guide:

  • Need dependency injection: service
  • Need discovery behavior: service tag
  • Need less boilerplate: autowiring
  • Need clarity or scalars: explicit arguments

If the question mentions container compilation or dependency resolution, services.yml is the answer.

Summary

services.yml and autowiring define how Drupal builds and wires its object graph. Together they enable clean dependency injection, extensibility, and performance. Understanding their roles is essential for Drupal 10 and 11 architecture and is frequently tested in Acquia certification exams.

This article prepares you for advanced topics such as compiler passes, service decoration, and container overrides.