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:
- All services.yml files are loaded
- Service definitions are merged
- Compiler passes modify or optimize definitions
- 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:
- All services.yml files are loaded
- Service definitions are merged
- Compiler passes are executed
- 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.