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! ππ