
PHP Advanced Concepts: Building a Custom PHP Dependency Injection Container
Dependency Injection Containers (DIC) manage the instantiation and lifecycle of objects in an application, allowing developers to define how dependencies should be resolved. By leveraging a DIC, you can easily swap implementations, making your code more flexible and easier to maintain.
Understanding Dependency Injection
Before we dive into the implementation, let’s clarify the concept of Dependency Injection. Essentially, DI allows an object to receive its dependencies from an external source rather than creating them itself. This is typically achieved using constructor injection, setter injection, or interface injection.
Types of Dependency Injection
| Type | Description |
|---|---|
| Constructor Injection | Dependencies are provided through the class constructor. |
| Setter Injection | Dependencies are set through setter methods after instantiation. |
| Interface Injection | Dependencies are provided through an interface that the class implements. |
Building the Dependency Injection Container
Step 1: Create the Container Class
We will start by creating a Container class that will hold our services and manage their instantiation.
<?php
class Container
{
private $services = [];
public function set($name, $service)
{
$this->services[$name] = $service;
}
public function get($name)
{
if (!isset($this->services[$name])) {
throw new Exception("Service not found: " . $name);
}
return $this->services[$name];
}
}Step 2: Registering Services
Next, we need a way to register services with our container. Services can be defined as closures that return an instance of the required class.
<?php
class Container
{
private $services = [];
public function set($name, callable $service)
{
$this->services[$name] = $service;
}
public function get($name)
{
if (!isset($this->services[$name])) {
throw new Exception("Service not found: " . $name);
}
return $this->services[$name]($this);
}
}Step 3: Example Usage
Let’s create a simple service and see how we can use our container to manage its dependencies.
<?php
class Database
{
public function connect()
{
return "Connected to the database.";
}
}
class UserService
{
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function getUser()
{
return $this->db->connect() . " Fetching user data.";
}
}
$container = new Container();
// Register services
$container->set('database', function () {
return new Database();
});
$container->set('user_service', function ($c) {
return new UserService($c->get('database'));
});
// Usage
$userService = $container->get('user_service');
echo $userService->getUser();Step 4: Adding Singleton Support
To enhance our container, we can implement singleton support, ensuring that only one instance of a service is created.
<?php
class Container
{
private $services = [];
private $instances = [];
public function set($name, callable $service, $singleton = false)
{
$this->services[$name] = $service;
if ($singleton) {
$this->instances[$name] = null;
}
}
public function get($name)
{
if (!isset($this->services[$name])) {
throw new Exception("Service not found: " . $name);
}
if (isset($this->instances[$name])) {
return $this->instances[$name];
}
$this->instances[$name] = $this->services[$name]($this);
return $this->instances[$name];
}
}Step 5: Registering Singleton Services
Now, we can register services as singletons by passing true as the third argument.
$container->set('database', function () {
return new Database();
}, true);Conclusion
Building a custom Dependency Injection Container in PHP allows for better organization and management of your application's dependencies. By following the principles of DI, you can create more maintainable and testable code. This tutorial covered the basics of creating a DIC, registering services, and implementing singleton support.
Learn more with useful resources:
