Dependency Injection (DI)
Chapter Overview
In this chapter you will learn:
- What Dependency Injection is
- Why DI exists
- Problems without DI
- Tight Coupling
- Loose Coupling
- Constructor Injection
- Method Injection
- Property Injection
- Dependency Inversion Principle
- Service Containers
- Laravel Examples
- PocketMine Examples
- ZyroNetwork Architecture
- Best Practices
- Enterprise Patterns
Introduction
Dependency Injection is one of the most important concepts in modern software engineering.
Frameworks like:
- Laravel
- Symfony
- PocketMine-MP
- Spring Boot
- ASP.NET
all heavily rely on Dependency Injection.
Without DI:
Large applications become difficult to maintain,
difficult to test,
and impossible to scale properly.
What is a Dependency?
A dependency is simply:
Something a class needs
in order to work.
Example:
class UserService {
}
UserService may need:
- Database
- Logger
- Config
- Cache
These are dependencies.
Real Life Example
Imagine a car.
A car depends on:
- Engine
- Wheels
- Fuel
Without them:
The car cannot function.
Similarly:
UserService
depends on:
Database
Logger
Cache
Problem Without Dependency Injection
Example:
class UserService {
private Database $database;
public function __construct()
{
$this->database =
new Database();
}
}
This works.
But it creates a huge problem.
Problem: Tight Coupling
Visualization:
UserService
↓
Database
The service is directly connected to Database.
You cannot replace it.
Problems Created
❌ Hard to test
❌ Hard to replace database
❌ Hard to scale
❌ Hard to maintain
❌ Violates SOLID principles
Example
Suppose later:
MySQL → MongoDB
Now you must rewrite everything.
Dependency Injection Solution
Instead of creating dependencies yourself:
new Database();
Receive them from outside.
Example:
class UserService {
public function __construct(
Database $database
) {
}
}
Now:
Someone else creates Database.
Visualization
WITHOUT DI:
UserService
↓
new Database()
WITH DI:
Application
↓
Database
↓
UserService
Much better.
Definition
Dependency Injection means:
Providing dependencies from outside
instead of creating them internally.
Types of Dependency Injection
There are three major types:
- Constructor Injection
- Method Injection
- Property Injection
Constructor Injection
Most common.
Example:
class UserService {
public function __construct(
private Database $database
) {
}
}
Usage:
$database =
new Database();
$service =
new UserService(
$database
);
Visualization
Database
↓
Constructor
↓
UserService
Why Constructor Injection?
Because dependencies become:
✅ Required
✅ Immutable
✅ Easy to understand
Enterprise Example
class AuthService {
public function __construct(
private Database $database,
private Logger $logger,
private Cache $cache
) {
}
}
Method Injection
Dependencies are passed to methods.
Example:
class UserService {
public function save(
Database $database
) {
}
}
Usage:
$service->save(
$database
);
When To Use
Useful when dependency is only needed once.
Property Injection
Example:
class UserService {
public Database $database;
}
Usage:
$service =
new UserService();
$service->database =
new Database();
Why Property Injection Is Bad
Problems:
❌ Dependency may not exist.
❌ Hard to track.
❌ Can become null.
Constructor Injection is usually preferred.
Tight Coupling
Bad Example:
class UserService {
private Database $database;
public function __construct()
{
$this->database =
new Database();
}
}
Loose Coupling
Good Example:
class UserService {
public function __construct(
Database $database
) {
}
}
Why Loose Coupling Matters
Because systems become replaceable.
Example:
MySQL
can become:
MongoDB
Redis
SQLite
without rewriting everything.
Interfaces + DI
This is where DI becomes extremely powerful.
Example:
interface DatabaseInterface {
public function query(
string $sql
);
}
Implementation:
class MysqlDatabase
implements DatabaseInterface {
}
Another:
class MongoDatabase
implements DatabaseInterface {
}
Service:
class UserService {
public function __construct(
DatabaseInterface $database
) {
}
}
Now:
Any database can be injected.
Amazing flexibility.
Visualization
UserService
↓
DatabaseInterface
↓
┌───────────────┐
│ MySQL │
│ MongoDB │
│ SQLite │
└───────────────┘
Dependency Inversion Principle
One of SOLID principles.
Rule:
Depend on abstractions,
not implementations.
Bad:
UserService
↓
MysqlDatabase
Good:
UserService
↓
DatabaseInterface
Service Container
A Service Container is:
A system that automatically
creates dependencies.
Example
Instead of:
$database =
new Database();
$service =
new UserService(
$database
);
Container does it automatically.
Laravel Example
public function __construct(
UserRepository $users
)
Laravel automatically injects:
UserRepository
Magic.
Internal Flow
Controller
↓
Container
↓
Create Dependencies
↓
Inject Dependencies
Example Container
class Container {
private array $services = [];
}
Register:
$container->set(
Database::class,
new Database()
);
Retrieve:
$container->get(
Database::class
);
PocketMine Example
PocketMine plugins often use managers.
Bad:
new RankManager();
new DatabaseManager();
new APIManager();
everywhere.
Good:
class Main {
private RankManager $ranks;
}
Inject managers.
ZyroNetwork Example
Architecture:
Main
│
├── DatabaseManager
├── RankManager
├── APIManager
├── FormManager
└── NetworkManager
Dependencies are injected.
Example:
class RankManager {
public function __construct(
DatabaseManager $database
) {
}
}
Visualization:
DatabaseManager
↓
RankManager
↓
APIManager
Real Enterprise Architecture
Controller
↓
Service
↓
Repository
↓
Database
Everything uses DI.
Testing Benefits
Suppose:
UserService
depends on:
DatabaseInterface
For testing:
class FakeDatabase
implements DatabaseInterface {
}
Inject fake object.
Testing becomes easy.
Unit Testing Example
$database =
new FakeDatabase();
$service =
new UserService(
$database
);
No real database needed.
Common Beginner Mistakes
Creating Objects Everywhere
Bad:
new Database();
new Logger();
new Cache();
inside every class.
Static Abuse
Bad:
Database::query();
everywhere.
Creates hidden dependencies.
Not Using Interfaces
Interfaces make systems flexible.
Good Example
AuthService
↓
DatabaseInterface
Bad Example
AuthService
↓
MysqlDatabase
Constructor Promotion Example
PHP 8:
class UserService {
public function __construct(
private Database $database,
private Logger $logger
) {
}
}
Very clean.
Dependency Graph
Application
│
├── Database
├── Logger
├── Cache
│
└── UserService
Large Enterprise Example
App
│
├── Controllers
├── Services
├── Repositories
├── Managers
├── Events
├── DTO
└── Contracts
Everything communicates using DI.
Best Practices
✅ Prefer Constructor Injection.
✅ Depend on Interfaces.
✅ Avoid static dependencies.
✅ Use Service Containers.
✅ Keep dependencies small.
Bad Signs
❌ Constructor has 15 dependencies.
Usually means:
Class has too many responsibilities.
Exercises
Exercise 1
Create:
DatabaseInterface
Exercise 2
Create:
MysqlDatabase
Exercise 3
Inject database into:
UserService
Mini Project
Create:
ZyroNetwork Authentication System
Classes:
AuthService
UserRepository
DatabaseManager
LoggerManager
CacheManager
Use Dependency Injection everywhere.
Interview Questions
What is Dependency Injection?
Providing dependencies from outside instead of creating them internally.
Why is DI useful?
Because it creates:
Loose Coupling
What are the three types of DI?
- Constructor Injection
- Method Injection
- Property Injection
Which type is preferred?
Constructor Injection
What principle does DI support?
Dependency Inversion Principle
Why do frameworks use DI?
For scalability, maintainability and testing.
Summary
Dependency Injection provides:
✅ Loose Coupling
✅ Easier Testing
✅ Better Architecture
✅ Flexible Systems
✅ Cleaner Code
✅ Enterprise Scalability
Dependency Injection is one of the foundations of modern PHP frameworks and large projects like:
- Laravel
- Symfony
- PocketMine Plugins
- ZyroNetwork APIs
- Enterprise Applications
References
https://phptherightway.com/#dependency_injection
https://martinfowler.com/articles/injection.html
https://laravel.com/docs/container
Next Chapter
➡ SOLID Principles
➡ Design Patterns
➡ Service Containers
➡ Repository Pattern
➡ MVC Architecture