Dependency Injection in PHP: Best Practices for Scalable Applications πŸš€

Dependency Injection in PHP: Best Practices for Scalable Applications πŸš€

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! πŸŽ‰πŸš€

Leave a Reply