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.

15 minutes · Symfony

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

  1. Put a class with one clear job into src/Service. Name it after the work, not after the technology.
  2. Do not put HttpFoundation Request or the entire container into it just because it is convenient. Pass needed data to the method instead.
  3. 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

  1. In a default Symfony project, classes in src/ are usually found automatically and made into services.
  2. Write a concrete type in the constructor, such as LoggerInterface. The container finds the matching object and passes it in.
  3. 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

  1. Where a supplier can change, depend on your own interface. For example, PaymentGateway instead of a concrete SDK client.
  2. 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.
  3. 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.

  1. 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
  2. Check the dependency type

    The command shows which services Symfony can pass for an interface or class.

    php bin/console debug:autowiring
  3. 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.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.