Dependency Injection Containers are vital in modern PHP development, facilitating efficient dependency management and promoting code flexibility. This guide offers insights into their core concepts and practical applications. By harnessing the power of the PHP Reflection API, developers can streamline dependency resolution and enhance application scalability. From basic principles to real-world implementations, this exploration aims to equip developers with the knowledge to leverage Dependency Injection Containers in PHP projects effectively.
Understanding Dependency Injection
Dependency injection is a software design pattern where one object provides the dependencies of another object. In this paradigm, objects receive their dependencies from external sources rather than creating them internally. Consider the following example:
class UserController extends Controller
{
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
}
Various Approaches to Object Injection
There exist several methods for injecting objects into classes, two prominent ones being:Constructor Injection: In this approach, object injection occurs through the class constructor. For example
class Profile
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
To instantiate the Profile class, you need to first instantiate the User class:
$user = new User;
$profile = new Profile($user);
Setter Injection:
class Profile
{
private $user;
public function setUser(User $user)
{
$this->user = $user;
}
}
To instantiate the Profile class, you have the option to inject the User object:
$user = new User;
$profile = new Profile();
$profile->setUser($user);
Understanding Dependency Injection Container
A Dependency Injection Container is a mechanism for handling the injection and retrieval of objects and third-party libraries within your application.
Many frameworks utilize the DI Container not only for the basic functionalities outlined in the preceding section but also for resolving dependencies.
But what exactly do we mean by “resolving” dependencies? It refers to the capability of your application to automatically manage the dependencies required by a specific class instead of performing this task manually.
Alright, let’s clarify further. Consider the following example:
class Profile
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Here’s how we instantiate the Profile class:
$user = new User;
$profile = new Profile($user);
So, to instantiate the Profile class, you must first instantiate the User class. This is how we manually resolve dependencies for the Profile class before instantiation.
We can neatly do this, here is a simple example of a Container class that resolves dependencies for your application automatically:
<?php
namespace App\Core;
use App\Core\Exceptions\DuplicateDependencyException;
use App\Core\Exceptions\UnresolvedParameterException;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
class Container
{
/**
* Array to store bindings for dependency injection.
*
* @var array
*/
protected $bindings = [];
/**
* Array to store resolved dependencies.
*
* @var array
*/
protected $resolved = [];
/**
* Array to store instances of resolved dependencies.
*
* @var array
*/
public $instances = [];
/**
* Bind a callback to the specified identifier for dependency resolution.
*
* @param string $abstract
* @param callable $callback
* @param bool $shared
* @throws \DuplicateDependencyException
*/
public function bind($abstract, $callback, $shared = false)
{
if (array_key_exists($abstract, $this->bindings)) {
throw new DuplicateDependencyException($abstract);
}
$this->bindings[$abstract] = [
'concrete' => $callback,
'shared' => $shared,
];
}
/**
* Register a singleton binding with the container.
*
* @param string $abstract
* @param callable $callback
* @return void
*/
public function singleton($abstract, $callback)
{
$this->bind($abstract, $callback, true);
}
/**
* Check if an abstract or identifier is bound in the container.
*
* @param string $abstract
* @return bool
*/
public function bound($abstract)
{
return array_key_exists($abstract, $this->bindings);
}
/**
* Create an instance of the specified identifier using dependency injection.
*
* @param string $abstract
* @param array $args
* @return mixed|null
*/
public function make($abstract, $args = [])
{
if (array_key_exists($abstract, $this->bindings)) {
$binding = $this->bindings[$abstract];
$concrete = $binding['concrete'];
if (is_callable($concrete)) {
if ($binding['shared'] && $this->resolved($abstract)) {
return $this->instances[$abstract];
}
$instance = $concrete($this, $args);
$this->storeResolvedInstance($abstract, $instance);
return $instance;
}
return $concrete;
}
return $this->resolve($abstract, $args);
}
/**
* Resolve dependencies and instantiate the specified class.
*
* @param string $abstract
* @param array $args
* @return mixed
* @throws \ReflectionException
* @throws \UnresolvedParameterException
*/
public function resolve($abstract, $args = [])
{
$refClass = new ReflectionClass($abstract);
$refClassName = $refClass->getName();
$constructor = $refClass->getConstructor();
$deps = $this->resolveDeps($refClass, $constructor, $args);
$instance = $refClass->newInstanceArgs($deps);
$this->storeResolvedInstance($refClassName, $instance);
return $instance;
}
/**
* Resolve method dependencies based on reflection and provided arguments.
*
* @param string $class
* @param string $method
* @param array $args
* @return array
* @throws \ReflectionException
* @throws \UnresolvedParameterException
*/
public function resolveDeps($class = null, $method, $args = [])
{
$deps = [];
if (!$method) {
return $deps;
}
$refMethod = $class
? ($method instanceof ReflectionMethod ? $method : new ReflectionMethod($class, $method))
: ($method instanceof ReflectionFunction ? $method : new ReflectionFunction($method));
$params = $refMethod->getParameters();
foreach ($params as $key => $param) {
$paramName = $param->getName();
$paramRefClass = $this->getClass($param);
if ($paramRefClass instanceof ReflectionClass) {
$className = $paramRefClass->getName();
$deps[] = $this->resolve($className);
} else if (array_key_exists($paramName, $args)) {
$deps[] = $args[$paramName];
} else if (array_key_exists($key, $args)) {
$deps[] = $args[$key];
} else if (!$param->isOptional()) {
throw new UnresolvedParameterException($paramName);
}
}
return $deps;
}
/**
* Check if a dependency is resolved and instantiated.
*
* @param string $abstract
* @return bool
*/
public function resolved($abstract)
{
return (
array_key_exists($abstract, $this->resolved) &&
array_key_exists($abstract, $this->instances)
);
}
/**
* Store a resolved instance and mark it as resolved.
*
* @param string $abstract
* @param mixed $instance
* @return void
*/
public function storeResolvedInstance($abstract, $instance)
{
$this->instances[$abstract] = $instance;
$this->resolved[$abstract] = true;
}
/**
* Get the ReflectionClass instance for the specified parameter type.
*
* @param \ReflectionParameter $parameter
* @return \ReflectionClass|null
* @throws \ReflectionException
*/
public function getClass($parameter)
{
/**
* @var \ReflectionNamedType $paramType
*/
$paramType = $parameter->getType();
if ($paramType && !$paramType->isBuiltin()) {
return new ReflectionClass($paramType->getName());
}
}
}
The Container class registers various classes using the bind() and singleton() methods:
$container = new Container();
$container->bind('Profile', function ($container) {
return $container->resolve(Profile::class);
});
$container->singleton('Profile', function ($container) {
return $container->resolve(Profile::class);
});
And whenever you need to instantiate the Profile class, you can simply execute the following:
$container->make('Profile');
Alternatively, you can instantiate the Profile class using its namespace without the need to bind it to the container.
$container->make(Profile::class);
Conclusion
In summary, we’ve explored the primary uses of the Dependency Injection Container, particularly auto writing, and how we effectively leverage DI through a simple implementation.
References:
This page was last edited on 28 July 2024, at 5:38 pm
How can we help you?























