Introduction to Dependency Injection (DI) in PHP
Modern PHP development is impossible without understanding the principles of loose coupling of components. Dependency Injection (DI) is one of the key design patterns that allows you to create flexible, testable, and easily maintainable applications. Instead of a class creating its own dependencies (e.g., a database object or logger), it receives them from the outside. This fundamentally changes the architecture of the code.
In this article, we will take a detailed look at what Dependency Injection in PHP is, what methods of injection exist, how a DI container works, and why this pattern has become the standard for modern frameworks such as Laravel, Symfony, and Yii2.
1. The Problem: Tight Coupling
Before talking about the solution, let's look at the problem. A classic example is the UserController class, which directly creates a Database object inside itself:
<?phpclass Database { public function query(string $sql): array { // Connecting to the database and executing the query return []; }}
class UserController { private Database $db;
public function __construct() { // Tight dependency — creating the object inside the constructor $this->db = new Database(); }
public function getUsers(): array { return $this->db->query('SELECT * FROM users'); }}?>Disadvantages of this approach:
- Testing complexity: You cannot replace
Databasewith a mock object in unit tests. - Violation of the Single Responsibility Principle (SRP): The class is responsible not only for business logic but also for creating dependencies.
- Modification complexity: If you want to replace MySQL with PostgreSQL, you will have to change the code in all classes where
Databaseis created.
2. The Solution: Dependency Injection
Dependency Injection proposes passing dependencies from the outside. There are three main methods of injection: through the constructor, through setters, and through an interface (method). Let's look at each.
2.1. Constructor Injection
The most common and recommended method. Dependencies are passed once when the object is created and cannot be changed later.
<?phpclass UserController { private Database $db;
// Dependency is passed from the outside public function __construct(Database $db) { $this->db = $db; }
public function getUsers(): array { return $this->db->query('SELECT * FROM users'); }}
// Usage:$db = new Database();$controller = new UserController($db);?>Pros: Guarantees that the dependency is always available; ideal for mandatory dependencies. Cons: If a class has many dependencies, the constructor becomes cumbersome.
2.2. Setter Injection
Suitable for optional dependencies that can be set after the object is created.
<?phpclass Logger { public function log(string $message): void { echo $message; }}
class UserController { private ?Logger $logger = null;
public function setLogger(Logger $logger): void { $this->logger = $logger; }
public function getUsers(): array { if ($this->logger) { $this->logger->log('Fetching users...'); } // Logic for fetching users return []; }}
// Usage:$controller = new UserController();$controller->setLogger(new Logger());?>Pros: