Dependency Injection in PHP: A Complete Guide with Code Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Dependency Injection in PHP: In Practice



Dependency Injection (DI) is one of the key SOLID principles that allows you to write flexible, testable, and maintainable code. In this article, we will break down how to implement dependency injection in PHP with real examples, without abstract theories. You will learn how to eliminate tight coupling between classes and make your code ready for change.



What is Dependency Injection and Why Do You Need It?



Imagine you have a UserController class that works with a database. Without DI, you would likely create the connection object directly inside the controller:



class UserController {    private $db;    public function __construct() {        $this->db = new MySQLConnection('localhost', 'user', 'pass');    }}


This approach creates tight coupling. If you want to switch to PostgreSQL or use a mock object for testing, you would have to change the controller's code. Dependency Injection solves this problem: dependencies are passed from the outside, not created inside the class.



Key advantages of DI:

  • Reduced code coupling
  • Simplified testing (easy to swap dependencies)
  • Reusability of classes
  • Clean architecture and adherence to the Dependency Inversion Principle (DIP)


Ways to Inject Dependencies in PHP



There are three main ways to inject dependencies. Let's look at each with examples.



1. Constructor Injection



The most common and recommended method. Dependencies are passed through constructor parameters and stored in class properties.



<?phpinterface DatabaseInterface {    public function query(string $sql): array;}

class MySQLDatabase implements DatabaseInterface { public function query(string $sql): array { // Real MySQL logic return ['id' => 1, 'name' => 'John']; }}

class UserRepository { private DatabaseInterface $db; public function __construct(DatabaseInterface $db) { $this->db = $db; } public function getUser(int $id): array { return $this->db->query("SELECT * FROM users WHERE id = $id"); }}

// Usage:$db = new MySQLDatabase();$repo = new UserRepository($db);$user = $repo->getUser(1);?>


Notice: we use the DatabaseInterface interface. This makes it easy to swap the implementation — for example, with a test stub.



2. Setter Injection



Used when a dependency is not required at the object creation stage or may change during operation.



<?phpclass MailService {    private ?MailerInterface $mailer = null;        public function setMailer(MailerInterface $mailer): void {        $this->mailer = $mailer;    }        public function send(string $to, string $subject, string $body): void {        if ($this->mailer === null) {            throw new \\RuntimeException('Mailer not configured');        }        $this->mailer->send($to, $subject, $body);    }}

// Usage:$mailService = new MailService();$mailService->setMailer(new SmtpMailer());$mailService->send('user@example.com', 'Hello', 'Body');?>


The downside of this approach is that you cannot guarantee the dependency will be set before calling the send() method. Therefore, the constructor is preferable for mandatory dependencies.



3. Method Injection



The dependency is passed directly to the method where it is needed. Useful for rare or context-specific dependencies.



<?phpclass ReportGenerator {    public function generate(DataFetcherInterface $fetcher, string $reportType): array {        $data = $fetcher->                

Blogs

Book Recommendations