Glossary

Dependency Injection: what it is and why to use it

A class’s dependencies are visible in its interface and can be replaced in a test or when the infrastructure changes.

Short definition

A class does not have to create everything it works with.

A dependency can be a repository, external API client, logger, message queue, or email sender. If a class creates such an object internally, it becomes tightly coupled to one implementation and harder to test. With dependency injection, it receives the required object when it is created.

This is a design principle, not a feature exclusive to Symfony. A framework can provide a DI container that automates creating and connecting services, but the same principle works without one by simply passing an object to a constructor.

Use cases

Where passing dependencies helps

It provides the most value at boundaries between application logic and its technical environment.

  • an order import with a marketplace client and repository
  • a shipping service with different carrier implementations
  • a use case invoked from an HTTP controller, CLI command, and message handler
  • a test without a real API, database, or queue
  • replacing infrastructure in one configuration location

Practical example

Importing a marketplace order

The import class needs to load an external order and store its local representation. It does not know or need to know whether the client speaks REST or the repository uses Doctrine. Those choices belong in application configuration.

interface MarketplaceOrderClient
{
    public function fetch(string $externalId): ExternalOrder;
}

final class ImportMarketplaceOrder
{
    public function __construct(
        private MarketplaceOrderClient $client,
        private OrderRepository $orders,
    ) {
    }

    public function import(string $externalId): void
    {
        $this->orders->save(Order::fromExternal($this->client->fetch($externalId)));
    }
}

How it works

From declaring a need to assembling the application

Required dependencies are clearest when declared directly in the constructor.

  1. Declaration The class states what it truly needs in its constructor.
  2. Composition Configuration or the composition root selects the concrete implementations.
  3. Use The class works through the public contract of the supplied object.
  4. Test A test can supply a controlled fake, stub, or mock instead of a remote service.
  5. Change The technical implementation can change without modifying business decisions.

Important features

Constructor injection, containers, and interfaces

DI helps only when it clarifies real boundaries and responsibilities.

Constructor injection

Suitable for dependencies without which an object cannot fulfil its role. The object cannot be created in a partially configured state. Setter injection is more appropriate for genuinely optional behaviour.

DI container and autowiring

The container records how to create and connect services. Autowiring handles unambiguous types; multiple implementations, scalar values, and special cases need explicit configuration.

Interfaces

An interface is useful for a real contract or genuinely interchangeable implementation. Creating one for every class only because of DI adds noise without value.

Service locator

With DI, a class openly declares dependencies. A general locator or entire container inside a class hides them instead; a narrowly scoped locator is an exception, not the usual replacement.

Benefits and limitations

DI does not automatically produce good architecture

Benefits

  • the class’s needs are readable directly from its constructor
  • a test does not have to open a real network connection or database
  • infrastructure can be changed in one place
  • helps distinguish a business rule from a technical detail

Common mistakes

  • a class with many dependencies instead of separated responsibilities
  • an interface for every internal class without a real contract
  • pulling the entire container into the domain, an entity, or a controller
  • assuming DI alone solves poor rules, data, or transactions

Scope of use

An abstraction should have a specific reason.

When a second marketplace is added, the import core can remain unchanged only if both sources genuinely share a meaningful contract. DI is not a reason to pretend there is a common interface where the business behaviour differs substantially.

A class with twelve required services is usually a sign that it combines too many responsibilities. The right response is not more container configuration, but simplifying the use case or dividing the service.

What to keep in mind

Clear dependencies instead of hidden global state

The principle is worth checking in tests and code review.

  • required dependencies through constructor injection
  • explicit configuration for ambiguous implementations
  • interfaces only for a real contract or behaviour change
  • do not pass the entire container into an ordinary application class
  • tests for important boundaries between logic and infrastructure

Common questions

What dependency injection is

Is dependency injection the same as a DI container?

No. Dependency injection is the principle of supplying dependencies. A DI container is a tool that can automate creating and connecting them.

Does every service need its own interface?

No. An interface suits a meaningful contract or genuinely interchangeable implementation. For one internal class without such a need, it merely adds another layer of code.

Why is constructor injection usually better than setter injection?

Required dependencies are visible and the object cannot be created without them. A setter is more suitable for genuinely optional behaviour.

Is a service locator always a mistake?

Not always; a narrowly scoped locator can suit lazy selection among predefined services. The entire container in an ordinary application class, however, usually hides dependencies and complicates testing.

How I use dependency injection in practice

I choose the number of layers according to the problem’s complexity.

In more complex areas, I separate application logic from databases, HTTP, and external services through clear dependencies and interfaces.

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.