Dependency Injection and Sub-Dependencies in php

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

  1. Container: A class or service that manages the instantiation and lifecycle of dependencies.
  2. Constructor Injection: Passing dependencies through the constructor.
  3. Sub-Dependencies: Automatically resolved by the DI container.

Example Scenario

We have the following:

  • A Database class that requires Config.
  • A UserRepository class that depends on Database.
  • A UserService class that depends on UserRepository.

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();
    }
}
PHP

Step 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();
PHP

Step 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);
    }
}
PHP

Register 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));
});
PHP

Resolve the Top-Level Dependency

$userService = $container->get(UserService::class);
$userService->listUsers();
PHP

Step 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();
PHP

This approach eliminates the need to manually define every dependency in the container.

Summary

  1. Manual DI is suitable for simple cases but doesn’t scale well.
  2. DI Containers simplify managing complex dependencies and sub-dependencies.
  3. Use frameworks or libraries with DI support for robust applications.
  4. Ensure your classes adhere to the Single Responsibility Principle (SRP) to make dependency injection effective.

Leave a Reply

Your email address will not be published. Required fields are marked *