Previous Next

Simplest usage case (2 classes, one consumes the other)

In the simplest use case, a developer might have one class (A) that is consumed by another class (B) through the constructor. By having the dependency injected through the constructor, this requires an object of type A be instantiated before an object of type B so that A can be injected into B.

namespace My {

    class A
    {
        /* Some useful functionality */
    }

    class B
    {
        protected $a = null;
        public function __construct(A $a)
        {
            $this->a = $a;
        }
    }
}

To create B by hand, a developer would follow this work flow, or a similar workflow to this:

$b = new B(new A());

If this workflow becomes repeated throughout your application multiple times, this creates an opportunity where one might want to DRY up the code. While there are several ways to do this, using a dependency injection container is one of these solutions. With Zend's dependency injection container Zend\Di\DependencyInjector, the above use case can be taken care of with no configuration (provided all of your autoloading is already configured properly) with the following usage:

$di = new Zend\Di\DependencyInjector;
$b = $di->get('My\B'); // will produce a B object that is consuming an A object

Moreover, by using the DependencyInjector::get() method, you are ensuring that the same exact object is returned on subsequent calls. To force new objects to be created on each and every request, one would use the DependencyInjector::newInstance() method:

$b = $di->newInstance('My\B');

Let's assume for a moment that A requires some configuration before it can be created. Our previous use case is expanded to this (we'll throw a 3rd class in for good measure):

namespace My {

    class A
    {
        protected $username = null;
        protected $password = null;
        public function __construct($username, $password)
        {
            $this->username = $username;
            $this->password = $password;
        }
    }

    class B
    {
        protected $a = null;
        public function __construct(A $a)
        {
            $this->a = $a;
        }
    }

    class C
    {
        protected $b = null;
        public function __construct(B $b)
        {
            $this->b = $b;
        }
    }

}

With the above, we need to ensure that our DependencyInjector is capable of seeing the A class with a few configuration values (which are generally scalar in nature). To do this, we need to interact with the InstanceManager:

$di = new Zend\Di\DependencyInjector;
$di->getInstanceManager()->setProperty('A', 'username', 'MyUsernameValue');
$di->getInstanceManager()->setProperty('A', 'password', 'MyHardToGuessPassword%$#');

Now that our container has values it can use when creating A, and our new goal is to have a C object that consumes B and in turn consumes A, the usage scenario is still the same:

$c = $di->get('My\C');
// or
$c = $di->newInstance('My\C');

Simple enough, but what if we wanted to pass in these parameters at call time? Assuming a default DependencyInjector object ($di = new Zend\Di\DependencyInjector() without any configuration to the InstanceManager), we could do the following:

$parameters = array(
    'username' => 'MyUsernameValue',
    'password' => 'MyHardToGuessPassword%$#',
);

$c = $di->get('My\C', $parameters);
// or
$c = $di->newInstance('My\C', $parameters);

Constructor injection is not the only supported type of injection. The other most popular method of injection is also supported: setter injection. Setter injection allows one to have a usage scenario that is the same as our previous example with the exception, for example, of our B class now looking like this:

namespace My {
    class B
    {
        protected $a;
        public function setA(A $a)
        {
            $this->a = $a;
        }
    }
}

Since the method is prefixed with set, and is followed by a capital letter, the DependencyInjector knows that this method is used for setter injection, and again, the use case $c = $di->get('C'), will once again know how to fill the dependencies when needed to create an object of type C.

Other methods are being created to determine what the wirings between classes are, such as interface injection and annotation based injection.

Previous Next