Understanding dependency injection
When we talk about the dependency injection, or in short DI, we talk about the simple task of, for example, injecting data in object or methods at initialization when needed by one or other higher up classes, which either modify or dispose off the object after use. The DI is probably the most complex feature in Zend Framework 2 to understand. Unfortunately because DI's over complexity in debugging and performance and the Service Locator (explained in Chapter 6, Modules, Models and Services). However, although it is not the best tool in the shed, we must try to learn it, because when mastered it could prove to be a very powerful tool to create a very maintainable piece of code.
If we come across a situation where it is necessary for us to input a lot of parameters in classes because of objects deeper in the code are dependent on them is probably the most annoying and un-maintainable piece of code that we can find in even the most professional environment. We need to think mainly about objects that are used more than once in an application, and always required to instantiate again.
How to do it…
Let us take a look at the following example and assume that FirstClass
is the only class that we will actually need further in the code:
namespace OneNamespace { class FirstClass { private $secondClass; public function __construct(SecondClass $secondClass) { $this->secondClass = $secondClass; } } class SecondClass { private $thirdClass; private $vehicle; public function __construct(ThirdClass $thirdClass, $vehicle) { $this->thirdClass = $thirdClass; $this->vehicle = $vehicle; } } } namespace AnotherNamespace { class ThirdClass { private $first_name; private $last_name; public function __construct($first_name, $last_name) { $this->first_name = $first_name; $this->last_name = $last_name; } } } // Let us now create the example through the classic // method. $thirdClass = new AnotherNamespace\ThirdClass("John", "Doe"); $secondClass = new OneNamespace\SecondClass($thirdClass, 'Motorcycle'); $firstClass = new OneNamespace\FirstClass($secondClass);
Both the preceding examples give either variables that are only used to instantiate another class and/or add complexity in reading the code. Although they both are correct, the use of DI can, in this case, make the configuration of both the classes much easier.
Initializing the DI at call-time
Let's take a look at this DI example, considering that we have the same classes as the preceding example:
namespace OneNamespace { class FirstClass { [..] } class SecondClass { [..] } } namespace AnotherNamespace { class ThirdClass { [..] } } // Instead of configuring all the classes, we will now // simply configure the Di, and only instantiate the // class that we want to use. $di = new \Zend\Di\Di(); $lister = $di->get( 'OneNamespace\FirstClass', array( 'first_name' => 'Jane', 'last_name' => 'Doe', 'vehicle' => 'Car', ) );
In the preceding example, we simply say to the DI that AnotherNamespace\ThirdClass
has two parameters in its __construct
method. The DI will then utilize Reflection
to find out what parameters are present there, and will then give any class that has a first_name
, vehicle
, or last_name
parameter in its constructor that parameter.
Of course we will see a potential flaw here, as you might need to utilize multiple instantiations, one can presume that at some point the same parameter name will be used. In our example, it would cause a problem if another class also has a $first_name
parameter but requires a different input, as the DI will simply give the one that is in its list.
Tip
If we use DI to instantiate our classes and all we need the constructor for is to set our variables, we can easily remove the constructor altogether as the DI doesn't use the constructor to initialize the variables. Instead the DI will just set the properties of the values.
One good thing about this is that this can flaw only happens when we use the DI at a call-time level, and not in a global configuration level as we will see now. That is why it isn't recommended to use the DI at call-time level at all.
Initializing the DI through a Configuration object
What we also can do to create a more specific (or accurate) initialization of our object – and to make sure classes with the same property names don't conflict – is initializing the DI with a configuration object.
The idea behind this is that we first create a configuration object (or array) that defines which classes need which properties set, and then use that to initialize the DI, which in its turn finds out when it needs to initiate what.
Take a look at the following example, which shows you the exact thing we just explained:
<?php // We are assuming that we are using the same classes as // in the previously shown examples. namespace OneNamespace { class FirstClass { [..] } class SecondClass { [..] } } namespace AnotherNamespace { class ThirdClass { [..] } } // After defining our classes we now begin to create our // configuration array which we will use to initialize // the DI. $configuration = array( // We want to use this specific configuration at // initialization of our class. 'instance' => array( // We specify the class name to use here 'SecondClass' => array( // We want to use this as a parameter 'parameters' => array( // The property name to fill is vehicle. 'vehicle' => 'Airplane' ), ), 'FirstClass' => array( // Again we want to use this as a parameter 'parameters' => array( // The property name to fill is first name and //last name. 'first_name' => 'Neil', 'last_name' => 'deGrasse Tyson', ), ), ), ); // We want to instantiate the Di\Configuration now. use \Zend\Di\Configuration; $diConfiguration = new Configuration($configuration); // Now instantiate the Di itself, with the configuration // attached. $di = new \Zend\Di\Di($configuration); // And to get the object we want to use, we just do the //same as before. $firstClass = $di->get('OneNamespace\FirstClass');
To make everything even nicer, we would just put the Zend\Di\Configuration
of the DI in the bootstrap of our module, so that we can use it easily throughout the namespace. This way we can simply put the configuration of the DI in our module.config.php
and let the framework take care of it.
How it works…
The DI or dependency injector is an important, and most of the time overlooked feature of Zend Framework 2. The DI makes our lives a lot easier by automatically finding the classes we need in our application.
With all its complexity however, comes a couple of features we should be wary of.
The DI only gives out one instance of an object
This means that every get()
call will result in the same instantiation over and over again. If we would like a new instance, we would need to call newInstance()
as the DI implements the singleton pattern, which means that all the data persists every time we call the get()
method unless we force a new instance of the DI.
Defining either all properties, or using a Fully Qualified (FQ) setter parameter
When our class has more properties than we define, we will find out that the DI will use the last value for every other property in the class. Of course this is unwanted, and if we wrote the class ourselves we should consider refactoring the configuration and/or class.
However, when there is no other way we can define the right properties only by using a Fully Qualified (FQ) setter parameter.
In our configuration we would then define a very specific property name, for example, class::method:paramPos
. If we take our ThirdClass
example from earlier on, this would then be ThirdClass::setFirstName:0
and ThirdClass::setLastName:0
respectively.
There's more…
There is loads more we can learn about the DI in Zend Framework 2. The following list provides a very short and compact description of other interesting DI components:
RuntimeDefinition
(default),CompilerDefinition
andClassDefinition
: These definitions are used to determine how to configure our objects. Although the default one usually does the job, it can't hurt to see what the other two Definitions do, because they all have their pros and cons.InstanceManager
: Used to define the configuration, specifically theAliases
,Parameters
andPreferences
.