Dependency-injection : internal workings
Now that you're fluent with dependency injection and inversion of control, we can start to talk about how to make a dependency injection container.
Concepts exposed here are simple, provide a structure for the container, and allow us to see clearly what is the good place and role of each class we made.
The Schema representation

In the Schema, and in the DI in general, a "service" is an object managed by the dependency injection container. As said in the precedent section, the Schema represents the way services and classes are linked together. It describes the dependencies of our classes.
If you know the abstract factory pattern, you can see the schema as a configuration when the container is the factory itself (or a kind of).
Schema contains all information about methods we have to call in order to inject our objects, arguments we have to inject, and all other information which is useful at the injection time.
In our example, the schema will contain information on which Icecream Alice depends, and which is the way to provide the good Icecream to her (the setIcecream method).
As far as now, we have talked about basics dependency rules. The Schema can handle many different types of Services, Methods and Arguments.
We tried to facilitate the extension steps, to add new types easily. All code we wrote is only dependent on a set of interfaces. So, it's possible to use any kind of methods, arguments or services. They just have to implement the right interface.
Here are the three interfaces that the Schema is dealing with: Services, Methods and Attributes.
Services
A service represents an object. Here, the Icecream is a Service, and Alice is another one.
A service is composed by:
- a name
- a set of methods
- a building process
- a scope
Scope is the way to control the life cycle of our services in the container: Does the service remain in the container during the whole script (singleton) or is it instantly removed from it (prototype)?
The actual instance of the injected object must be the same for all services if the scope is defined as singleton, or each time a different one if the scope is set to prototype.
Other scopes could be imagined like the "session" scope that would provide the same instance through a unique session, or a kind of "immortal" scope that would use persistence features to make an instance immortal through user sessions.
Our DI container comes with a set of different services types:
- Default
- A simple service, composed by methods, and which can be built as a simple object.
- Aliases
- A service which is an alias for another one. Just the name is different. It allows us to manage our dependencies in the time. "For now, it's an alias, but maybe, one day,(haha) it'll be another type of service".
- Inherited services
- Rather than repeating ourselves multiple times, we can use inheritance in our service definition. It's just like inheritance in programming: all methods and services you redefine or add inside this service will override the inherited services.
Methods
Each service contains Methods.
Methods are used to inject some parameters in our services, or define some resources which have to be called at the construction time. In the Alice example, one method is setIcecream.
A method is composed by:
- a name,
- an optional class name
- a set of arguments
- information describing if it's static or not
Here is the different types of implemented methods:
- Default
- Simple method, with arguments.
- Attribute methods
- Used to directly set public attributes
$service->attribute = $value. This type of method can contain only one argument. It can appear weird to manage attributes like methods. It's important to understand the difference between methods and arguments. Arguments represents values whereas methods represents ways to set them. - Callbacks
- Before, or after the creation of your service, you can call specific methods, called callback methods.
Arguments
Methods contains arguments, and there are different types of arguments. Arguments are the end of the chain service / method / argument.
Here are the different types of arguments:
- Default
- Native php types (int, string, float etc.)
- Container Argument
- This one represents the container itself. This option is used only for services that needs to use the container. These services are called "ContainerAware" services.
- Current Service Argument
- It's possible to use the injected service as an argument. In practice, it's just used in callbacks methods, that need to be notified after the creation of a service.
- Empty Value Argument
- A argument which has no special value (container argument and service argument extends this one).
- Service Reference Argument
- One of the most used type of argument. It represents another service.
- Service Resolved Argument
- Sometimes, it's useful to use another service method to get a argument. Think about configuration for example. This type of argument relies on another service method to be resolved.
Construction strategies

Now that we have a Schema representation of our objects, we have to build (construct) them.
We've chosen to separate completely the construction logic and the definition logic. Schema is a definition step, and building our services, and injecting them is a construction step.
Each Schema type relies on a construction strategy. There are as many types of construction strategies as schema definition types. (eg. Services, methods and arguments)
Each definition type can build itself, calling the build method. In fact, internally, it's possible to build each schema type with different construction strategies. This way of processing allows us (and you!) to easily add new construction ways, with very tiny classes.
Builders
Defining the Schema with objects and classes is not really "cool", and it can take some time to describe each argument, service, method, by writing class calls etc.

To avoid this annoying behavior, an interesting way to proceed is to provide and use builders. Builders are objects that can read specific Schemes formats to build the Schema representation (with objects) which is understandable for us (and for the builder).
The first type of builder coming to my mind, is the XML builder. It is able to read schemes defined in an XML based syntax, and provide the good type of Schema. Our XML builder comes with a XSD definition file.
Some Java dependency injection container (Google Juice and Spring uses annotation in the code to interact with the container.
Annotations are text, that's fit to the class implementation which provide information on what needs to be injected, and how.
Whereas it's not the default and recommended behavior, it is possible in Spiral's DI to generate a Schema representation thanks to these annotations.
The DI comes with theses builders:
- XML Builder
- PHP Builder (with a fluent interface)
- Annotations (uses reflection to get annotation in classes and build the schema)
Dumpers
On the other side, it can be useful to use information provided by schema in order to create other types of contents.

It's possible to write the Schema thanks to a specific Builder, and to dump it in another format. Our DI comes with an interesting dumper, that allows us to dump the schema in a graphic representation.
It's easy to show the dependencies of your application, by simply calling the DotDumper (Dot is the format used by graphviz).
Here is the list of built-in dumpers:
- Text dumper
- Dot Dumper
- XML Dumper
Namespaces
The Dependency Injection container is separated into the following namespaces:
- The
Constructionnamespace, which contains all construction related classes (the construction strategies) - The
Definitionnamespace, which contains the Schema. - The
Transformationnamespace, which contains Builders and Dumpers.