Dependency Injection (DI) is a design pattern used in software development to manage dependencies among objects. It involves providing an object with its dependencies rather than having the object create them itself.
Handling sub-dependencies arises when an object depends on another object, which in turn has its own dependencies. Here’s how you can manage DI and sub-dependencies effectively in PHP.
Key Components
- Container: A class or service that manages the instantiation and lifecycle of dependencies.
- Constructor Injection: Passing dependencies through the constructor.
- Sub-Dependencies: Automatically resolved by the DI container.
Example Scenario
We have the following:
- A
Database
class that requiresConfig
. - A
UserRepository
class that depends onDatabase
. - A
UserService
class that depends onUserRepository
.
Step 1: Define Classes and Dependencies
class Config {
private array $settings;
public function __construct(array $settings) {
$this->settings = $settings;
}
public function get($key) {
return $this->settings[$key] ?? null;
}
}
class Database {
private Config $config;
public function __construct(Config $config) {
$this->config = $config;
}
public function connect() {
echo "Connecting to database using DSN: " . $this->config->get('dsn') . PHP_EOL;
}
}
class UserRepository {
private Database $database;
public function __construct(Database $database) {
$this->database = $database;
}
public function getAllUsers() {
$this->database->connect();
echo "Fetching all users" . PHP_EOL;
}
}
class UserService {
private UserRepository $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function listUsers() {
$this->userRepository->getAllUsers();
}
}
PHPStep 2: Manual Dependency Injection
You can manually wire up dependencies. This works for simple cases but becomes cumbersome as the complexity grows.
$config = new Config(['dsn' => 'mysql:host=localhost;dbname=test']);
$database = new Database($config);
$userRepository = new UserRepository($database);
$userService = new UserService($userRepository);
$userService->listUsers();
PHPStep 3: Using a Dependency Injection Container
A DI container automates the creation and injection of dependencies.
Implement a Simple DI Container
class Container {
private array $instances = [];
public function set(string $key, callable $resolver) {
$this->instances[$key] = $resolver;
}
public function get(string $key) {
if (!isset($this->instances[$key])) {
throw new Exception("No entry found for $key.");
}
return $this->instances[$key]($this);
}
}
PHPRegister Dependencies
$container = new Container();
// Register Config
$container->set(Config::class, function () {
return new Config(['dsn' => 'mysql:host=localhost;dbname=test']);
});
// Register Database
$container->set(Database::class, function ($c) {
return new Database($c->get(Config::class));
});
// Register UserRepository
$container->set(UserRepository::class, function ($c) {
return new UserRepository($c->get(Database::class));
});
// Register UserService
$container->set(UserService::class, function ($c) {
return new UserService($c->get(UserRepository::class));
});
PHPResolve the Top-Level Dependency
$userService = $container->get(UserService::class);
$userService->listUsers();
PHPStep 4: Using a Framework
For real-world applications, you can use a framework with built-in DI support, like:
- Laravel: With its Service Container.
- Symfony: With its Dependency Injection component.
- PHP-DI: A lightweight standalone DI library.
Step 5: Handle Sub-Dependencies Automatically
A more advanced DI container like PHP-DI can automatically resolve sub-dependencies using reflection, reducing boilerplate.
use DI\ContainerBuilder;
require 'vendor/autoload.php';
$builder = new ContainerBuilder();
$container = $builder->build();
// Resolve UserService with sub-dependencies
$userService = $container->get(UserService::class);
$userService->listUsers();
PHPThis approach eliminates the need to manually define every dependency in the container.
Summary
- Manual DI is suitable for simple cases but doesn’t scale well.
- DI Containers simplify managing complex dependencies and sub-dependencies.
- Use frameworks or libraries with DI support for robust applications.
- Ensure your classes adhere to the Single Responsibility Principle (SRP) to make dependency injection effective.
Leave a Reply