Practical guide
How to use Dependency Injection correctly in Symfony
A class should not secretly create everything it needs. Pass dependencies to it and it becomes easier to test and change.
First, the short version
What is it about?
Dependency Injection means that a class does not create a logger or database client for itself. It receives them from the outside, most often in its constructor.
A service is an object with one useful job. Symfony’s container creates services, connects them, and can automatically pass them where their type is needed.
Get ready
What you need
A running Symfony project and one small service that you want to use from a controller or another object are enough.
- A Symfony project with standard services.yaml configuration.
- One clear responsibility, such as generating an order number or sending a notification.
- An interface where you want to hide a concrete supplier, such as an email or payment service.
- A basic test or a place from which you can call the service. It will immediately show whether it is wired correctly.
Steps 1 to 3
Pass dependencies simply
Start with constructor injection. It is readable, required dependencies are visible at once, and it is easy to test.
1. Create a small service
- Put a class with one clear job into src/Service. Name it after the work, not after the technology.
- Do not put HttpFoundation Request or the entire container into it just because it is convenient. Pass needed data to the method instead.
- If the service needs another service, add it as a typed constructor argument.
mkdir -p src/Service Official Symfony service container documentation 2. Let Symfony use autowiring
- In a default Symfony project, classes in src/ are usually found automatically and made into services.
- Write a concrete type in the constructor, such as LoggerInterface. The container finds the matching object and passes it in.
- When two implementations of the same interface exist, choose one explicitly in services.yaml. Guessing hurts later.
php bin/console debug:autowiring LoggerInterface Official Symfony autowiring documentation 3. Use interfaces at service boundaries
- Where a supplier can change, depend on your own interface. For example, PaymentGateway instead of a concrete SDK client.
- Choose the concrete implementation in configuration. Application code then does not need to know whether it is a live service, test double, or local fake.
- A service with ten dependencies is a signal to reduce its responsibility. Do not solve it by adding an eleventh.
php bin/console debug:container --tag=app.service Official Symfony service configuration documentation Step 4
Check the wiring before adding more code
The container can tell you what it knows and where a dependency is missing.
-
Find your service in the container
Replace App\Service\OrderNumberGenerator with your class name. It lists the definition and its arguments.
php bin/console debug:container App\Service\OrderNumberGenerator -
Check the dependency type
The command shows which services Symfony can pass for an interface or class.
php bin/console debug:autowiring -
Run a small test
In a test, pass a simple substitute for the dependency and check the result. That is where constructor injection pays off.
php bin/phpunit
If something goes wrong
Common problems
Cannot autowire service
The container does not know the argument type or cannot choose an implementation. Check the namespace, constructor type, and add an explicit interface-to-implementation binding in services.yaml if needed.
php bin/console debug:autowiring You pass the entire container into a service
It is convenient only on day one. Pick concrete dependencies and put them in the constructor. The code then shows what it truly needs and the test does not build half the application.
The controller contains all business logic
A controller should accept a request and return a response. Move order rules, calculations, or integration logic to a small service that the controller calls.
Changing one service breaks ten tests
The service probably has too many responsibilities or depends on concrete implementations. Split it by use case and use interfaces at the boundaries.
Done
Dependencies are no longer hidden.
Dependency Injection now helps you keep classes small and readable. Apply the same principle to every new service.