Dependency injection
1Presentation
Dependency injection allows logic to be decoupled between objects by using the inversion of control principle.
Temma automatically creates a component to handle dependency injection into business objects. This component is the backbone
that all objects in your application can rely on to access each other.
It is available in controllers under the private attribute $_loader.
By default, the component contains the following:
- $loader->dataSources: Registry used to access the data source connection objects (see the controllers documentation).
- $loader->session: Session management object (see sessions).
- $loader->config: Configuration management object, which gives access to all configuration directives.
- $loader->request: Management object of the incoming request, which allows to handle the execution flow of the framework.
- $loader->response: Object used by the framework to manage the response to the client.
- $loader->controller: Instance of the plugin or the controller in use.
2Usage with your DAOs
DAO objects can be instantiated in controllers using their _loadDao() method. Outside controllers, the dependency injection component can be used to create instances of DAO objects you have developed.
For example, if you have created a UserDao object (in lib/UserDao.php), you can use it as follows:
3Usage with your business objects
An object that implements the \Temma\Base\Loadable interface can be loaded by the component. Its constructor must then take a single parameter, of type \Temma\Base\Loader.
Here is an example of an object that writes data to a var/list.txt file (in the project tree):
- Line 1: The object implements the \Temma\Base\Loadable interface.
- Line 3: A private attribute will contain the full path to the file in which we will write later.
-
Line 9: Constructor of the object, which receives as a parameter an instance of the dependency injection component.
- Line 10: We build the path to the file, using the configuration object, whose varPath attribute gives the path to the var/ directory of the project.
- Line 17: The write() method can be used to write to the file.
For this object to be loadable by the framework, it must be accessible in the inclusion paths of the project. By default, this means that we will save it in a file named List.php, placed in the lib/ directory of the project.
From that point on, the object is available directly as if it were an attribute of the component. Only one instance of the object will be created (the first time the object is called).
Here is an example of a controller that uses the List object thanks to the component:
- Line 5: We go through the component to access the List object, then to its write() method.
Now imagine that we create another business object, loadable via the component, which uses the List object.
- Line 3: Unlike the List object, this object will keep the instance of the dependency injection component in a private attribute.
- Line 10: In the constructor, we copy the instance of the component (received as a parameter) into the private attribute.
- Line 21: We use the component to call the List object.
This illustrates how business objects can call each other, with the component that handles accesses and instantiations.
4Alternative access
The examples seen previously assume that the objects managed by the component are placed in the root namespace (in other words, they are not in an explicit namespace), and that their codes are in files placed in the lib/ directory of the project.
But sometimes you're going to want to use objects that are in deep namespaces. In this case, you will have to access the objects using an associative array type writing, and no longer object oriented.
For example, if we want to use the add() method of the \Math\Base\Compute object, we will have to write:
It is also possible to use the get() method:
5Explicit additions in the component
You also have the option of creating the object yourself and adding it to the component by specifying the name you want to give it:
You can add any element to the component (not just objects that implement the \Temma\Base\Loadable interface):
6Additions by callback
It is also possible to assign an anonymous function. This function will be executed during the first call via the component; it must return the object which will then be returned by the component.
Here is an example of a controller:
- Line 3: We use the __wakeup() method to initialize the controller.
- Line 4: We create the calc key in the component, by assigning an anonymous function to it. This function takes a single parameter, which will receive the component instance. The function must return the object which will be returned later.
- Lines 5 to 7: The variable $this refers to the controller itself. We look at the value contained by the param template variable to know which implementation will be returned.
- Line 13: We assign to the param template variable the value received in the $type parameter.
- Line 14: We use the component without having to worry about which implementation is used.
- Line 19: We use the component. Here it will always be the \Math\Other\Compute object that is used, but that could change without needing to modify the action.
7Additions by builder
A builder is a function that takes care of managing instantiations, and that we register with the
setBuilder() method of the component.
This function takes two parameters: the first is an instance of the component; the second is the name of the object we are trying to access.
Here is an example:
8Alias management
8.1Defining aliases
It's possible to define naming aliases, which will be used when an object (necessarily implementing the \Temma\Base\Loadable interface) is called via the loader.
You can declare an alias using the setAlias() method:
You can also declare multiple aliases by passing an associative array to the setAliases() method:
It's possible to delete a previously defined alias, by supplying the value null for the same alias name, either with the setAlias() method or with the setAliases() method.
8.2Configuring aliases
Rather than defining aliases with the setAlias() and setAliases() methods, it's easier to list them in the project configuration.
For example, with the following etc/temma.php file:
You can write the following code:
8.3Callback alias management
When an alias is defined (either by the setAlias() and setAliases() methods or in the configuration file), it is possible to define a callable value for it. In this case, when the element is requested from the loader, the pointed function is executed (passing the loader instance as a parameter), and its return is used as the value associated with the requested name.
Example of code with a function:
Example with an anonymous function:
Example with an object:
Example with a static object:
8.4Using aliases for testing
When running automated tests on a Temma application, you may need to replace an object with a mock, a “fake object” that simulates the behavior of the original object.
With naming aliases, it becomes very easy to create a file etc/temma.test.php (if the runtime environment is called test), which redefines the loaded objects for a given name.
Example of an etc/temma.php file:
Example of an etc/temma.test.php file:
Your application code may contain:
9Prefix management
9.1Introduction to prefixes
In addition to aliases, it's also possible to define naming prefixes, which summarize namespaces (or parts of namespaces). These prefixes can then be used at the beginning of the name of an object managed by the loader.
Caution: Avoid listing too many prefixes, as they are all reviewed each time an object is requested for the first time. This can have an impact on performance.
9.2Defining prefixes
You can declare a prefix using the setPrefix() method:
You can also declare multiple prefixes by passing an associative array to the setPrefixes() method:
It's possible to delete a previously defined prefix, by supplying the value null for the same prefix, either with the setPrefix() method or with the setPrefixes() method.
9.3Prefix configuration
Rather than defining prefixes with the setPrefix() and setPrefixes() methods, it's easier to list them in the project configuration.
Example of a etc/temma.php file:
You can then write the following code:
9.4Callback prefix management
When a prefix is defined (either via the setPrefix() and setPrefixes() methods or in the configuration file), it is possible to define a callable value for it. In this case, when the element is requested from the loader, the pointed function is executed (passing the loader instance and the requested object name from which the prefix has been removed as parameters), and its return is used as the value associated with the requested name.
Code example with a function:
As with aliases, you can pass the name of a function, an anonymous function, a static call string (e.g. 'MyObject::myMethod') or an array of calls to an instantiated object (e.g. [$object, 'myMethod']).
10Redefinition of the loader in configuration
Rather than explicitly call the setBuilder() method, it is possible to specify a loader object in the etc/temma.php file (see the configuration documentation). This object must inherit from the \Temma\Base\Loader class, and contain a protected builder() method. This method must take the name of the object to instantiate as a parameter, and return an instance of the latter.
Here is an example. To start, the Temma configuration in the etc/temma.php file:
Then, in the lib/MyLoader.php file:
Then it becomes possible to write: