PHP Dependency Injection Container for Beginners

Miqayel Srapionyan
5 min readApr 16, 2023

--

If you are a PHP developer, you might have heard about the DI Container. A DI Container is an object responsible for managing the instantiation of services (or objects) in your application. It is a core part of most modern PHP frameworks, including Symfony, Laravel, Yii, etc. This article will explore the basics of PHP DI Container and how to use it in your PHP application.

What is a DI Container?

A DI Container is a mechanism for managing the instantiation of services in your application. It is an object that keeps track of the dependencies of your services and can create instances of them when requested. In other words, it is a registry of services that your application needs to function properly.

A DI Container helps you manage the complexity of your application by providing a centralized place to manage your dependencies. It allows you to define services once and use them throughout your application without worrying about the implementation details.

How to use a DI Container?

To use a DI Container in your PHP application, you need to follow these steps:

Step 1: Define your services
The first step is to define your services. A service is a PHP object that performs a specific task in your application. For example, a database connection object is a service that your application needs to connect to the database.
To define a service, you need to create a class that implements the functionality you need. For example, if you need a database connection object, you can create a class called “Database” that handles the connection to the database.

Step 2: Register your services
The next step is to register your services with the DI Container. This is done by creating an instance of the DI Container and adding your services to it. You can do this in your application bootstrap file or in a separate file that is included in your application.
To register a service, you need to give it a unique name and specify its implementation. For example, to register the “Database” service, you can use the following code:

$container = new Container();

$container->register('database', function() => {
return new Database();
});

Step 3: Retrieve your services
Once your services are registered, you can retrieve them from the DI Container when needed. To retrieve a service, you can use the get method on the DI Container instance.

For example, to retrieve the “Database” service, you can use the following code:

$database = $container->get('database');

This will create a new instance of the Database class and return it.

Step 4: Use your services
Once you have retrieved your services, you can use them in your application. For example, if you retrieved the “Database” service, you can use it to connect to the database and execute queries.

$database = $container->get('database');
$connection = $database->connect();
$result = $connection->query('SELECT * FROM users');

Let's talk about the container itself. In PSR there is an interface for DI Containers (PSR-11). It has just two methods, get and has . How to implement the register bind or setmethod decides the programmer.

Here is an example with comments of a DI Container.

<?php

declare(strict_types = 1);

namespace App;

use App\Exceptions\Container\ContainerException;
use Psr\Container\ContainerInterface;

class Container implements ContainerInterface
{
/**
* @var array
*/
private array $instances = [];

/**
* @param string $key
*
* @return mixed|object|null
*/
public function get(string $key)
{
if ($this->has($key)) {
$entry = $this->instances[$key];

return $entry($this);
}

return $this->resolve($key);
}

/**
* @param string $key
*
* @return bool
*/
public function has(string $key): bool
{
return isset($this->instances[$key]);
}

/**
* @param string $key
* @param callable $concrete
*
* @return void
*/
public function register(string $key, callable $concrete): void
{
$this->instances[$key] = $concrete;
}

/**
* Resolve the given class.
*
* @param string $key
*
* @return mixed|object|null
* @throws \ReflectionException
*/
public function resolve(string $key)
{
// Inspect the class that we are trying to get from the container
$reflectionClass = new \ReflectionClass($key);

if (! $reflectionClass->isInstantiable()) {
throw new ContainerException('Class "' . $key . '" is not instantiable');
}

// Inspect the constructor of the class
$constructor = $reflectionClass->getConstructor();

if (! $constructor) {
return new $key;
}

// Inspect the constructor parameters (dependencies)
$parameters = $constructor->getParameters();

if (! $parameters) {
return new $key;
}

// If the constructor parameter is a class then try to resolve that class using the container
$dependencies = array_map(
function (\ReflectionParameter $param) use ($key) {
$name = $param->getName();
$type = $param->getType();

if (! $type) {
throw new ContainerException(
'Failed to resolve class "' . $key . '" because param "' . $name . '" is missing a type hint'
);
}

if ($type instanceof \ReflectionUnionType) {
throw new ContainerException(
'Failed to resolve class "' . $key . '" because of union type for param "' . $name . '"'
);
}

if ($type instanceof \ReflectionNamedType && ! $type->isBuiltin()) {
return $this->get($type->getName());
}

throw new ContainerException(
'Failed to resolve class "' . $key . '" because invalid param "' . $name . '"'
);
},
$parameters
);

return $reflectionClass->newInstanceArgs($dependencies);
}
}

As you can notice we are using PHP Reflection API to resolve class. You can read more about Reflection here.

Benefits of using a DI Container

Using a DI Container has several benefits:

  1. Centralized configuration: All of your services are configured in one place, making it easier to manage and modify them.
  2. Dependency injection: DI Containers can automatically inject dependencies into your services, making it easier to manage complex dependencies.
  3. Lazy-loading: DI Container can delay the instantiation of services until they are needed, improving performance and reducing memory usage.
  4. Testing: DI Containers make it easier to write tests for your services by allowing you to replace them with mock objects.

Conclusion

In conclusion, a DI Container is a powerful tool for managing the dependencies in your PHP application. By using a DI Container, you can centralize your service configuration, manage complex dependencies, and improve performance.

--

--

Miqayel Srapionyan

Software Engineer with a passion to learn technologies and share in stories.