Implementing Dependency Injection: Clean Code in PHP
Stop hard-coding dependencies inside your classes. Learn how to implement dependency injection in PHP to build decoupled, testable, and maintainable code.

Previously in this course, we reached finalizing-mvc-integration-connecting-your-php-architecture, where we successfully connected our routes to controllers and models. Today, we’re taking a critical step toward professional-grade code by implementing dependency injection.
The Problem: Hard-Coded Dependencies
Up until now, your controllers likely instantiate their dependencies directly. For example, if your UserController needs to fetch data, it might look like this:
PHPclass UserController { public function index() { #6A9955">// Hard-coded dependency $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass'); $users = $db->query("SELECT * FROM users")->fetchAll(); #6A9955">// ... render view } }
This approach creates a "hard-coded dependency." Your controller is now responsible for creating the database connection. If you want to change the database settings, or if you want to swap the database for a mock object while testing, you have to modify the controller code itself. This violates the principle of decoupling and makes your application rigid.
Dependency Injection from First Principles
Dependency injection (DI) is a design pattern where an object receives other objects it relies on, rather than creating them internally. Instead of the class being responsible for "finding" its dependencies, we "inject" them from the outside, usually through the constructor.
Think of it like a coffee machine. A machine with a built-in grinder is hard to fix if the grinder breaks. A professional machine, however, accepts ground coffee as an input. It doesn't care where the coffee came from, only that it has it. By passing the dependency, we make the machine (or in our case, the class) flexible.
Refactoring to Constructor Injection
To implement this, we move the instantiation out of the controller and into the constructor.
PHPnamespace App\Controllers; use PDO; class UserController { private PDO $db; #6A9955">// We require the dependency in the constructor public function __construct(PDO $db) { $this->db = $db; } public function index() { $users = $this->db->query("SELECT * FROM users")->fetchAll(); #6A9955">// ... } }
Now, when you want to use the UserController, you must provide the PDO instance:
PHP$db = new PDO(...); $controller = new UserController($db); $controller->index();
Improving Testability
Why does this matter? If you want to write a unit test for your controller, you don't want to connect to a real, live database. With constructor injection, you can pass a "mock" or a "fake" database object that mimics the PDO interface without actually executing SQL. This is how you achieve the decoupling discussed in introduction-to-mocking-and-stubs-isolating-your-unit-tests.
Hands-on Exercise
In your current MVC project:
- Identify a controller that creates a Model or a PDO instance inside one of its methods.
- Create a private property for that dependency.
- Update the
__constructmethod to accept the dependency as an argument and assign it to the property. - Update your entry point (where you instantiate the controller) to pass the required dependency.
Common Pitfalls
- Over-injection: Don't inject 10 different services into one class. If a class needs that many, it likely has too many responsibilities (a violation of the Single Responsibility Principle).
- Global State Dependency: Avoid using
global $dbinside your classes. It hides dependencies rather than making them explicit. Always use constructor injection instead. - Passing the "Container": Resist the urge to inject a "Service Container" into your controllers. Inject only the specific services the class needs.
FAQ
Q: Does dependency injection make the code more complex? A: It adds a few lines of boilerplate, but it makes your code significantly easier to refactor and test. For professional applications, the benefits far outweigh the setup cost.
Q: Should I inject everything? A: Only inject dependencies that are external services (Database, Mailers, Loggers). You don't need to inject simple value objects or configuration arrays.
Recap
Dependency injection is a core pillar of clean software architecture. By using constructor injection, we move the responsibility of dependency creation outside of our classes, which leads to better decoupling, makes our classes easier to test, and keeps our OOP logic clean and professional.
For those looking to scale their architecture, exploring the service-layer-pattern-achieving-true-decoupling-in-laravel is the natural next step in mastering these concepts.
Up next: We will learn how to use traits for code reuse to keep our classes focused and lean.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

Custom WordPress Theme Development
A custom WordPress theme built exactly to your design — fast, clean, and easy to manage. No bloated page builders, no compromises.

