Author: Samuel Adeshina
Posted on: 2015-11-09
Package: PHP Dependency Injection Container
Read this article all the various ways Dependency Injection can be used to improve your project structure, how Dependency Injection containers work, and how to build a Dependency Injection Container from scratch looking at a real Dependendency Injection Container package that autoload dependency classes only when they are needed.
Contents
Introduction
Injecting Dependencies Via A Constructor
Injecting Dependencies Via A Setter Method
The Need For A Dependency Injection Container
PDI: An Autoloading PHP Dependency Injection Container package
Conclusion
Introduction
I am beginning this article by giving a real life example script I just finished debugging. I had a Database Abstraction Class that helped me to manage communications to and from a database, which was working fine except errors and exceptions were not handled properly.
Instead of "trying and catching" all the exceptions within the class, I decided to write an Exception Manager class. This class provides a setter method that accepts an error id and logs the error after notifying the user.
Although, my Database class could easily inherit the Exception manager class, a Service provider or a Dependency Injection container provided more advantages because they remove the overhead that comes with class inheritance and makes it easy to develop both objects independent of each other.
The code snippet below shows a very "compressed" version of both classes.
<?php class DBManager { public function __construct() { } public function contains_error( $param ) { return (count($param) < 1) ? false: true; } public function query( $queryString ) { $result = retrieve( $queryString ); if(contains_error($result)) { throw new \Exception(1); } } } class ExceptionManager { public static $exceptionID; public function __set($value) { process_exception($value); } public function process_exception($param) { log($param); puts($param); } } ?>
There are different ways the ExceptionManager object can be passed into the DBManager class as a dependency. Below follows examples of all three ways and a rewrite of the necessary parts of the above code snippet to explain them.
Injecting Dependencies Via A Constructor
A dependency can be injected into an object by passing its instance into the constructor of that object. In simpler terms, you can inject a dependency by passing as a parameter, an instance of the dependency into the __construct
method of your class. For instance, assuming class A needs to inject B, B can be injected via the constructor this way:
<?php class A { public function __construct (B $instanceofB) { //do something with $instanceofB (you can call it's various methods and properties) } } ?>
Going back to our first example with the DBManager and ExceptionManager objects, I can inject the ExceptionManager class into my DBManager class via the constructor as shown below:
<?php class DBManager { private $exceptionManager; public function __construct( ExceptionManager $emObject) { $this->exceptionManager = $emObject; } public function contains_error($param) { return (count($param) < 1) ? false: true; } public function query($queryString) { $result = retrieve( $queryString ); if( contains_error( $result ) ) { $this->exceptionManager::exceptionID = 1; } } } ?>
From the above, we can see that instead of "throwing a new exception", our ExceptionManager class is now handling all errors and exceptions properly and then injecting this object into the DBManager class made the whole process seamless.
Injecting Dependencies Via A Setter Method
Another way a dependency can be injected into an object is via a method that accepts an instance of it as a parameter and then store it in a class variable. Just like in the example above where we equated $exceptionManager, a class variable to the instance of ExceptionManager class that was passed in via the constructor.
For a better understanding of this, let's go through the constructor method again.The constructor which is a "special" kind of method accepts an instance of the dependency as a parameter. It then stores this instance in a class variable and every other method and property can interact and use the object value of the class variable from any scope.
A method which is usually referred to as a setter method in most literature, can be created to do this. As an example, let's create a setter method for our DBManager class.
<?php class DBManager { private $exceptionManager; public function __construct() { } public function setDependency( ExceptionManager $emObject) { $this->exceptionManager = $emObject; } public function contains_error( $param ) { return (count($param) < 1) ? false: true; } public function query( $queryString ) { $result = retrieve( $queryString ); if( contains_error( $result ) ) { $this->exceptionManager::exceptionID = 1; } } } ?>
The setDependency method above does with the dependency exactly what the constructor does with it in the first example. It accepts an instance of the ExceptionManager class and sets the $exceptionManager class variable equal to this new instance.
The Need For A Dependency Injection Container
So far, we've been treating the case where we need only one dependency. In most real-life scenarios our DBManager might not only need an ExceptionManager class, it could also be dependent on a DateTime class for logging the time period of query execution or an ArrayParser class, an Iterator class or even all four at the same time.
An object is not limited to the number of dependencies it requires at a time, In fact thats the purpose of Object Oriented Designs, code reusability, we use and reuse other objects instead of rewriting them all over again.
So back to the question, our DBManager is dependent on the following objects: an ArrayParser object, a DateTime object, an Iterator object and an ExceptionManager object. How do we inject all these into our dependent class?
A dependency Injection Container or most commonly known as IoC containers, (Inversion of Control containers) can be used to handle Dependency Injection calls.
We can also use a very simple methodology to load our dependencies using the setter or constructor methods approaches. We can just pass them all as parameters or via an array. This will do, but it is not advisable. It is best to always use a Dependency Injection container or a Service provider.
There are various Dependency Inject containers for PHP such as PHP-DI, Pimple and numerous others. A search for "Dependency Injection" under the PHP tag on github would return tons of packages.
PDI: An Autoloading PHP Dependency Injection Container package
The PDI package is more of a service provider that automatically knows when and where you need a dependency and injects it into your dependent object using an autoloader.
Usually, to inject your dependencies (if you are not using an IoC container), you use an interface, a constructor or a setter method to "manually" inject the dependency into your program.
With PDI, you do not have to do all these, you just call an instance of the dependency object and PDI injects it into the dependent class for immediate use.
For example, using PDI we could use the ExceptionManager class inside the DBManager class this way:
<?php class DBManager extends \PDI { public function __construct() { } public function contains_error( $param ) { return (count($param) < 1) ? false: true; } public function query( $queryString ) { $result = retrieve( $queryString ); if (contains_error( $result )) { $this->ExceptionManager::exceptionID = 1; $this->DateTime->log(time()); } } } ?>
The DBManager class inherits the PDI class, this makes it possible to use the ExceptionManager class without injecting it via any method, PDI does this automatically. Assuming we already have a DateTime object that contains a log method, we can also load and use it automatically without first injecting it or extending, just like we did above.
PDI contains no public method or interface, it's main job is to "find" and "provide" (or inject) any dependency into your project. Internally, it has a setter and getter method that creates a variable with the name of the class which is your dependency and gets an object instance of that class, respectively.
In other words, you provide the name of the class you need to use and PDI returns an object instance of that class on the fly. I illustrate how PDI works with another example below.
You have a Car class that is dependent on an Automobile and an Engine class. The Automobile class contains two public methods called "setWheel" and "getWheel", while the Engine class contains a public property called "engineType" that accepts a string value. The code snippet below explains how both dependencies (Automobile and Engine classes) can be injected into our Car class using PDI
<?php class Car extends \PDI { public $numberOfWheel; public $engineType; public $automobileObject; public $engineObject;xxx public function setParameters() { $this->Automobile->setWheel(4); $this->Engine->engineType = "24AE1"; }xxx public function getParameters() { $this->numberOfWheel = $this->Automobile->getWheel(); $this->engineType = $this->Engine->engineType; } } ?>
As we can see from the above, we did not create a new instance of either of the Automobile and Engine class. We just used them "as if" they have been instantiated before. Calling
$this->Automobiletriggered PDI's call to the
__getmethod and the object of the Automobile class was returned immediately, then we could use all the methods and properties inside it, just like we also did for the Engine class
PDI completely supports PHP 7 and is still under active development but you can download it from the PHP Classes site . You can also clone it from github.
Conclusion
We have seen why dependency injection is important and how to properly inject dependencies into our programs. We talked about the PDI service provider and used code samples to explain how it can be used. In the final article to this series, I will talk about the PHP-DI and Pimple containers, compare them and explain how they both work, individually.
If you liked this article or would like to ask a question about how the PHP DI package works to make dependency injection more elegant, post a comment here.
You need to be a registered user or login to post a comment
Login Immediately with your account on:
Comments:
2. Unnecessary booleans - Per Persson (2015-11-10 20:53)
Use of "condition ? false : true"... - 1 reply
Read the whole comment and replies
1. Then, PDI is now the unique superclass ? - steven (2015-11-09 09:43)
Then, PDI is now the unique superclass ?... - 1 reply
Read the whole comment and replies