Menu
Coddy logo textTech

Dependency Injection

Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 57 of 91.

In the previous lesson, you saw how composition lets a Car contain an Engine object. But there's a problem with creating dependencies inside the class - it makes the code rigid and hard to test. Dependency Injection solves this by passing dependencies from the outside rather than creating them internally.

Compare these two approaches:

<?php
// Without DI - dependency created inside
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // Tightly coupled
    }
}

// With DI - dependency passed in
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // Injected from outside

The second approach is dependency injection. The Car doesn't create its own engine - it receives one. This simple change has powerful benefits: you can pass different engine types, swap implementations for testing, and the class becomes more flexible.

For even greater flexibility, inject interfaces instead of concrete classes:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

Now Car works with any class implementing EngineInterface - a gas engine, electric motor, or a mock for testing. This decoupling is why dependency injection is fundamental to writing maintainable, testable PHP applications.

challenge icon

Challenge

Easy

Let's build a notification system that demonstrates the power of dependency injection. Instead of hardcoding how messages are sent, you'll create a flexible system where the delivery method can be swapped out easily.

You'll organize your code across four files:

  • MessageSenderInterface.php — Define an interface called MessageSenderInterface with a single method send(string $recipient, string $message): string. This interface establishes the contract that any message sender must follow.
  • EmailSender.php — Create an EmailSender class that implements MessageSenderInterface. Include the interface file. The send() method should return "Email to [recipient]: [message]".
  • NotificationService.php — Create a NotificationService class that receives its dependency through the constructor. Include the interface file. The class should:
    • Accept a MessageSenderInterface in its constructor using constructor promotion
    • Have a method notify(string $recipient, string $message) that delegates to the injected sender and returns its result
    Notice how NotificationService depends on the interface, not a concrete class. This means you could inject any sender that implements the interface without changing the service.
  • main.php — Include the EmailSender and NotificationService files. You'll receive two inputs: a recipient and a message. Create an EmailSender, inject it into a NotificationService, then call notify() and print the result.

This pattern keeps your NotificationService flexible — it works with any sender you inject, whether that's email, SMS, or something you haven't built yet. The service doesn't create its own dependencies; it receives them from the outside.

Cheat sheet

Dependency Injection (DI) is a technique where dependencies are passed into a class from the outside rather than created internally.

Without DI (tightly coupled):

<?php
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // Created inside
    }
}

With DI (loosely coupled):

<?php
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // Injected from outside

For maximum flexibility, inject interfaces instead of concrete classes:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

This allows Car to work with any class implementing EngineInterface, making the code more maintainable and testable.

Try it yourself

<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';

// Read input
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));

// TODO: Create an EmailSender instance
// TODO: Inject it into a NotificationService
// TODO: Call notify() and print the result

?>
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming