Lesson 16 โข Advanced
Dependency Injection ๐
Build testable, decoupled PHP applications with dependency injection containers and service providers.
What You'll Learn in This Lesson
- โข Why tight coupling makes code untestable and fragile
- โข Constructor, setter, and interface injection techniques
- โข Build a DI container with bind, singleton, and resolve
- โข Service providers for organizing registration logic
- โข The "depend on abstractions, not concretions" principle
DI Fundamentals: Tight vs Loose Coupling
Without DI, classes create their own dependencies โ hardcoding which database, logger, or mailer to use. This makes swapping implementations impossible and unit testing a nightmare. With DI, dependencies are passed in from outside, so you can inject real services in production and mock objects in tests.
Try It: DI Basics
See the difference between tight coupling and dependency injection
// Dependency Injection: The Core Concept
console.log("=== WITHOUT Dependency Injection (Tight Coupling) ===");
console.log();
class MySQLDatabase {
connect() { return "Connected to MySQL"; }
query(sql) { return "MySQL result for: " + sql; }
}
// โ BAD: Class creates its own dependency
class UserServiceBad {
constructor() {
this.db = new MySQLDatabase(); // Hardcoded! Can't swap or test
}
getUsers() { return this.db.query("SELECT * FROM users"); }
}
let bad = new UserServiceBad(
...Building a DI Container
A DI container is a registry that knows how to create and wire all your application's services. You register factories (bind) or singletons, and the container resolves the full dependency tree automatically. Frameworks like Laravel, Symfony, and PHP-DI all provide containers โ but understanding how they work from scratch makes you a better developer.
Try It: DI Container
Build a container with bind, singleton, and resolve โ auto-wire services
// DI Container: Auto-Wiring Dependencies
console.log("=== Building a Simple DI Container ===");
console.log();
class Container {
constructor() {
this.bindings = new Map();
this.singletons = new Map();
}
// Register a factory function
bind(name, factory) {
this.bindings.set(name, { factory, singleton: false });
console.log(" ๐ฆ Bound: " + name);
}
// Register as singleton (created once, reused)
singleton(name, factory) {
this.bindings.set(name, { factory, si
...โ ๏ธ Common Mistakes
public function __construct(private readonly UserRepository $users) {}๐ Quick Reference โ Dependency Injection
| Concept | Description |
|---|---|
| Constructor Injection | Pass dependencies via constructor (preferred) |
| Setter Injection | Pass dependencies via setter methods |
| bind() | Register a factory โ new instance each time |
| singleton() | Register once โ same instance reused |
| resolve() | Get an instance from the container |
| Service Provider | Organizes bindings into logical groups |
๐ Lesson Complete!
You can now build decoupled, testable PHP apps with DI! Next, learn advanced error handling with custom exception hierarchies.
Sign up for free to track which lessons you've completed and get learning reminders.