
Dependency injection lets Laravel services depend on contracts instead of concrete details, improving testing and flexibility.
Dependencies reveal design
A class dependency tells you what the class needs to do its work. If a service creates its own HTTP client, logger, repository, and mailer inside a method, those dependencies are hidden. Hidden dependencies make tests harder and couple the class to specific tools. Dependency injection makes collaborators visible and replaceable.
Constructor injection
Constructor injection is the default choice for required dependencies. A payment service can receive a PaymentGateway, an OrderRepository, and a LoggerInterface. The constructor shows the service contract clearly. Laravel resolves these dependencies through the container, so application code can ask for the service without manually building the object graph.
Interfaces and bindings
An interface is useful when multiple implementations exist, when the dependency talks to the outside world, or when tests need a fake. Bind PaymentGateway to StripePaymentGateway in a service provider. In tests, bind it to FakePaymentGateway. The checkout workflow remains unchanged because it depends on the contract.
Method injection
Method injection works well for dependencies that are specific to one controller action or job handler. Laravel can inject Request, services, or route models into methods. Use it when the dependency is not part of the long-term identity of the class. For core collaborators, constructor injection is usually clearer.
Avoiding service locator habits
The container is powerful, but calling app(SomeClass::class) throughout business logic hides dependencies again. It becomes a service locator. Prefer explicit constructor parameters. Use the container at the application edge, in service providers, factories, or framework integration points.
Testing with dependency injection
DI shines in tests. A report generator can receive an in-memory repository. A notification workflow can receive a fake sender. A payment action can receive a gateway that returns controlled responses. The test does not need network access or a real database for every unit of behavior.
Connection to patterns
Adapter, Strategy, Repository, and Observer all become easier when dependencies are injected. DI is the wiring that lets those patterns stay decoupled.
Internal reading path
This article is part of a connected OOP and design patterns series. Continue with these related guides:
SOLID Principles in PHP: A Practical Guide for Real Projects
Adapter Pattern in PHP: Wrapping Third-Party Services Safely
Service Layer in Laravel: Keeping Controllers Thin and Workflows Clear
Practical checklist
Name the responsibility before choosing a pattern.
Prefer small contracts where behavior varies or external services are involved.
Keep controllers at the edge and move workflows into named application code.
Add tests around the behavior that is most likely to change.
Use patterns to reduce coupling, not to make simple code look advanced.