Managing dependencies in PHP applications can get messy if you donβt structure your code properly. Dependency Injection (DI) is a powerful design pattern that helps you:
β
 Write cleaner, more maintainable code
β
 Improve testability with unit testing
β
 Decouple components for better scalability
In this guide, weβll explore what dependency injection is, why itβs important, and how to implement it using real-world examples.
π― 1οΈβ£ What is Dependency Injection (DI)?
π‘ Dependency Injection is a technique where you inject dependencies (objects) into a class instead of creating them inside the class.
Without Dependency Injection (Bad Practice)
<?php
class Database {
    public function connect() {
        return "Connected to database";
    }
}
class User {
    private $db;
    public function __construct() {
        $this->db = new Database(); // β Hardcoded dependency
    }
    public function getUser() {
        return $this->db->connect();
    }
}
?>
π₯ Whatβs the problem?
β Tightly coupled β If we need a different Database implementation, we must modify the User class.
β Hard to test β Cannot easily replace Database with a mock in tests.
With Dependency Injection (Best Practice)
<?php
class Database {
    public function connect() {
        return "Connected to database";
    }
}
class User {
    private $db;
    public function __construct(Database $db) {
        $this->db = $db; // β
 Injecting dependency
    }
    public function getUser() {
        return $this->db->connect();
    }
}
$db = new Database();
$user = new User($db); // Injecting Database object
echo $user->getUser();
?>
π₯ Why is this better?
β
 Decouples the classes β The User class doesnβt depend on how the Database class works.
β
 Easier to test β We can inject a mock database for testing.
β
 More flexible β We can replace Database with another implementation without modifying User.
2οΈβ£ Types of Dependency Injection in PHP
There are 3 main ways to inject dependencies:
| Type | How it Works | Example | 
|---|---|---|
| Constructor Injection | Pass dependencies via the class constructor | new User(new Database()); | 
| Setter Injection | Pass dependencies via setter methods | $user->setDatabase(new Database()); | 
| Interface Injection | Pass dependencies via an interface | class User implements DatabaseAware {} | 
1οΈβ£ Constructor Injection (Recommended)
<?php
class Logger {
    public function log($message) {
        echo "[LOG]: $message";
    }
}
class User {
    private $logger;
    public function __construct(Logger $logger) {
        $this->logger = $logger; // Injecting dependency
    }
    public function createUser($name) {
        $this->logger->log("User '$name' created.");
    }
}
$logger = new Logger();
$user = new User($logger); // Injecting Logger
$user->createUser("Zero Dev");
?>
π₯ Why is this the best approach?
β
 Forces dependencies to be passed at object creation.
β
 Ensures a valid state (dependencies are always available).
2οΈβ£ Setter Injection (Alternative)
<?php
class Logger {
    public function log($message) {
        echo "[LOG]: $message";
    }
}
class User {
    private $logger;
    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }
    public function createUser($name) {
        if (!$this->logger) {
            echo "No logger set!";
            return;
        }
        $this->logger->log("User '$name' created.");
    }
}
$user = new User();
$user->setLogger(new Logger()); // Injecting Logger via setter
$user->createUser("Zero Dev");
?>
π₯ When to use this?
β
 When the dependency is optional.
β But it allows creating objects without setting dependencies (may cause errors).
3οΈβ£ Interface Injection
Best when enforcing a contract for multiple implementations.
<?php
interface LoggerInterface {
    public function log($message);
}
class FileLogger implements LoggerInterface {
    public function log($message) {
        file_put_contents("log.txt", "[LOG]: $message\n", FILE_APPEND);
    }
}
class User {
    private $logger;
    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    public function createUser($name) {
        $this->logger->log("User '$name' created.");
    }
}
$logger = new FileLogger();
$user = new User($logger);
$user->createUser("Zero Dev");
?>
π₯ Why use interface injection?
β
 Makes your code flexible β You can use different loggers (FileLogger, DBLogger, etc.).
β
 Enforces consistency β Ensures all loggers implement log().
3οΈβ£ Using Dependency Injection Containers (DIC)
π‘ Manually injecting dependencies works fine for small projects, but larger apps need a Dependency Injection Container (DIC) to manage dependencies automatically.
A DIC is a class that registers and resolves dependencies for you.
Example: Simple PHP DI Container
<?php
class Container {
    private $bindings = [];
    public function bind($key, $resolver) {
        $this->bindings[$key] = $resolver;
    }
    public function resolve($key) {
        return $this->bindings[$key]();
    }
}
$container = new Container();
// Register dependencies
$container->bind('logger', function () {
    return new Logger();
});
// Resolve and inject dependencies
$logger = $container->resolve('logger');
$user = new User($logger);
$user->createUser("Zero Dev");
?>
π₯ Whatβs happening?
β
 The container manages object creation.
β
 You donβt need to manually instantiate dependencies.
β
 Great for scalable applications.
π― Mini Project: Dependency Injection in a User Authentication System
Letβs build a real-world authentication system using DI.
1οΈβ£ Database Class
<?php
class Database {
    public function connect() {
        return "Connected to database!";
    }
}
?>
2οΈβ£ AuthService Class
<?php
class AuthService {
    private $db;
    public function __construct(Database $db) {
        $this->db = $db;
    }
    public function login($username, $password) {
        return "Logging in $username using database connection: " . $this->db->connect();
    }
}
?>
3οΈβ£ Index.php (Injecting Dependencies)
<?php
require "Database.php";
require "AuthService.php";
$db = new Database();
$auth = new AuthService($db); // Injecting Database
echo $auth->login("ZeroDev", "password123");
?>
π₯ Why is this powerful?
β
 Decouples authentication from database logic.
β
 Easier to replace the Database class with another implementation.
π Final Thoughts
Now you know how to use Dependency Injection like a pro!
β
 Constructor Injection (Recommended)
β
 Setter Injection (For optional dependencies)
β
 Interface Injection (For multiple implementations)
β
 DIC for scalable applications
π Next: Using PHP Composer
Happy coding! ππ
