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.

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

Practice on your own: Online PHP compiler