Oh, no way... another Dependency Injection container written in PHP?
Well, in fact it is, but the purpose of this project is academic, to discuss some interesting topics about what is the best approach the building of a Dependency Injection Container.
Doppo is a basic DIC implementation, with some features (nothing new, just what I think is the most important and useful) implemented. Is developed using TDD, with several refactoring iterations through several architectural changes.
So, what is all about? Let's take a look at the features.
Doppo container needs to be built with two required parameters. First of all we need to define the configuration we want to compile, and second, we need to define the debug flag.
use Doppo\Doppo;
$configuration = [];
$debug = true;
$doppo = new Doppo(
$configuration,
$debug
);
This is an empty Doppo container, we can see that the configuration is empty, and is created in debug mode. This information is not modifiable anymore, so is like a fingerprint for a container instance.
We could not define this object as immutable because when is compiled, the
object's state changes from an external point of view.
The $configuration
value is what defines how this container has to be compiled
and how all defined services must be built. Let's see the full definition.
$configuration = array(
'my_service' => array(
'class' => 'My\Class\Namespace',
'arguments' => array(
'@my_other_service',
'~my_parameter',
'simple_value',
)
),
'my_other_service' => array(
'class' => 'My\Class\Namespace',
),
'my_parameter' => 'parameter_value',
);
Configuration allow two types of elements: Services and Parameters. The
difference between them from the point of view of the DIC is that any service
definition must contain a class value, so if the definition is an array, and the
class
key exists, this will be treated as a service.
Otherwise, will be treated as a parameter.
As you can see, when we define the arguments of a service, we can refer to
another service using the prefix @
, we can also refer to a parameter value
using the prefix ~
or we can just pass a plain value, like a string, an array
or an object.
The only way of passing dependencies to a service is by its constructor. Some other Containers allow you to build a service using setters or public variables, but this one does not. Considering that a service dependency must be injected in the constructor, otherwise is not a dependency but a configuration, container should only know about building, not configuring, at least while not implemented specifically.
Once the container is built with a configuration and a debug mode, we must compile it. At that point, container will build internally a structure to serve properly service instances and parameter values.
use Doppo\Doppo;
$configuration = array(
'my_service' => array(
'class' => 'My\Class\Namespace',
'arguments' => array(
'@my_other_service',
'~my_parameter',
'simple_value',
)
),
'my_other_service' => array(
'class' => 'My\Class\Namespace',
),
'my_parameter' => 'parameter_value',
);
$debug = true;
$doppo = new Doppo(
$configuration,
$debug
);
$doppo->compile();
The container compilation can only be done once. If we compile a compiled container, an Exception is thrown.
Once the container is compiled you can retrieve any defined service instance by
using the method get
and any defined parameter value using the method
getParameter
.
$myServiceInstance = $doppo->get('my_service');
$myParameterValue = $doppo->getParameter('my_parameter');
When you retrieve a service instance, this one is only build once. It means that internally, when any service is required and built, the resultant instance, just before returning it, is stored locally. When the service is called again, existent instance will be returned instead of building it again.
The problem here is that the container is built and compiled every time is needed. This means that all the container will be processed and checked in every execution, what makes it very inefficient.
This package also provides an extension of the main class Doppo. Is called CacheableDoppo and is built like this.
use Doppo\Doppo;
$configuration = [];
$debug = true;
$cachePath = '/tmp/doppo.cache.php';
$doppo = new Doppo(
$configuration,
$debug,
$cachePath
);
This new class works as previous one, both implement same interface
ContainerInterface
, but this one adds a caching layer above the first one.
When the container is compiled, a cache file is built and stored where specified
as the last constructor parameter.
Each time the container needs to be built, if the cache file is already created, this one will be loaded providing a set of methods defining all the service construction specification.
The container can also be decorated. A decorator class must also implement
ContainerInterface
and can add some behavior to specified interface without
changing old implementation.
This decorator logs, depending on the container debug mode, all external interaction with the public container API.
use Doppo\Doppo;
$configuration = [];
$debug = true;
$cachePath = '/tmp/doppo.cache.php';
$logger = new Logger();
$doppo = new LoggableDecorator(
new Doppo(
$configuration,
$debug,
$cachePath
),
$logger
);
Logger instance must implements Psr\Log\LoggerInterface
provided by the
package Psr\Log
. You can see some public implementations in these
Package list