Friday, September 28, 2012

(outdated) Using the ServiceManager as an Inversion of Control Container (Part 2)

Note: This post has been superseded. Check out the Part 1 and Part 2. The post you are reading now is outdated, and there is a much better way of doing this, in my opinion. 

Note about superseded posts: The new Part 1's changes mostly involve coding style and use of design patterns. In the outdated Part 2, I attempted to instantiate multiple instances of objects using the AbstractPluginManager. This worked for simple things, but got complicated when one of the non-shared instantiated instances had dependencies. The best I could work out involved creating factory factories, which created the required factories, which then created the model I wanted. Even without dependencies, the AbstractPluginManager method still required creating those extra factory classes for the sole purpose of creating objects, which leads to a plethora of files in bigger applications. Another deficiency of the outdated version is that it still has hard-coded dependencies in the form of type-hinting.

In the Part 1, we learned how to inject a single, shared dependency into the controller using the ServiceManager as an inversion of control container, but what if you need multiple instances of a class? For example, what if you needed multiple Brick objects to build that Building?

<?php
namespace Building\Model;

class Brick
{
    protected $color;

    public function __construct($options = array())
    {
        if (!is_array($options))
        {    
            throw new \DomainException('$options must be an array');
        }    
        $this->color = (empty($options['color']))?"default":$options['color'];
    }

    public function setColor($color)
    {
        $this->color = $color;
    }

    public function getColor()
    {
        return $this->color;
    }
}
~    

That is where the AbstractPluginManager comes in. It extends ServiceManager, but its get() method allows an extra parameter, $options, which allows you to initialize each new instance with a unique configuration. Even though it is simplified and only needs one option, I set up the Brick class to match this pattern, allowing the $options array to be passed in to the constructor. I'll get to the implementation of the AbstractPluginManager after a little more setup.

Lets add a BrickFactory class as a dependency for our Building. Adding a little functionality for actually doing something with the Bricks, Building now looks like this:

<?php
namespace Building\Model;

class Building
{
    protected $brickFactory;
    protected $bricks;
    protected $colors = array("red", "brown", "black", "yellow", "orange", "purple", "green");

    public function __construct(BrickFactory $brickFactory)
    {
        $this->brickFactory = $brickFactory;
    }

    public function getNewBrick($color)
    {
        $factory = $this->brickFactory;
        $brick = $factory->get('Brick', array('color'=>$color));
        return $brick;
    }

    public function addLayer($color=null)
    {
        $layer = array();
        for ($i=0; $i<6; $i++)
        {    
            $newBrickColor = ($color === null)?$this->colors[array_rand($this->colors)]:$color;
            $brick = $this->getNewBrick($newBrickColor);
            $layer[] = $brick;
        }    
        $this->bricks[]=$layer;
    }

    public function getBricks()
    {
        return $this->bricks;
    }

}

With the new constructor, you can no longer instantiate a Building without a BrickFactory, so it can no longer be an invokable in the serviceConfig. We also need to specify how to create a BrickFactory. Update Module.php's getServiceConfig() method to look like this:

    public function getServiceConfig()
    {
        return array(
            'factories'=>array(
                'ViewFactory'=>function($sm)
                {    
                    $viewFactory = new Model\ViewFactory();
                    $viewFactory->setInvokableClass('ViewModel', 'Zend\View\Model\ViewModel');
                    return $viewFactory;
                },  
                'BrickFactory'=>function($sm)
                {    
                    $brickFactory = new Model\BrickFactory();
                    $brickFactory->setInvokableClass('Brick', 'Building\Model\Brick', false);
                   return $brickFactory;
                },  
                'Building'=>function($sm)
                {    
                    $pluginManager = $sm->get('BrickFactory');
                    $building = new Model\Building($pluginManager);
                    return $building;
                },  
            ),  
        );  
    }
Notice that we also added a ViewFactory so that the ViewModel is not a hard dependency in the controller. Isn't it convenient that the ViewModel fits into this pattern, as an invokable with an optional array constructor parameter?
The difference between these two factories is that ViewModel will be shared, meaning every time you call $factory->get('ViewModel') it will return the same instance, while Brick will return a brand new instance every time. The third parameter in the setInvokableClass function sets whether the class is shared, and defaults to true.
You might see examples in the ZF2 source where invokeableClasses is set directly in the class implementing abstractPluginManager, like the following:

$protected invokableClasses = array('db', 'firephp');
I'd recommend using the setter, as I did in Module.php, as it takes care of canonicalization of the name, and you can use the same name format that you use everywhere else (e.g. 'ViewModel' instead of 'viewmodel').

So what is this BrickFactory? It is simply a class to create new Bricks. The key to having it work is the fact that AbstractPluginManager extends ServiceManager, which means it can have its own invokable classes and factories.This means that when we ask it for a Brick, it sees it as an invokable class and instantiates it (since we specified it as invokable in the closure), including the $options array in the constructor if it was specified. The implementation is as simple as this:

<?php
namespace Building\Model;

use Zend\ServiceManager\AbstractPluginManager;

class BrickFactory extends AbstractPluginManager
{
    public function validatePlugin($plugin)
    {
        if ($plugin instanceof Brick)  
        {    
            return;
        }    
        throw new \DomainException('Invalid Brick Implementation');
    }

}
The only function we are required to implement is validatePlugin, which checks to see if it is the correct type, and any other checking you need to do.
The ViewFactory is nearly identical:

<?php
namespace Building\Model;

use Zend\ServiceManager\AbstractPluginManager;
use Zend\View\Model\ViewModel;

class ViewFactory extends AbstractPluginManager
{
    public function validatePlugin($plugin)
    { 
        if ($plugin instanceof ViewModel)
        { 
            return;
        } 
        throw new \DomainException('Invalid ViewModel Implementation');
    } 

}

Finally, here is the new BuildingController and the new index.phtml

<?php

namespace Building\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Building\Model\Building;
use Building\Model\ViewFactory;

class BuildingController extends AbstractActionController
{
    protected $building;
    protected $viewFactory;
 

    public function __construct(Building $building, ViewFactory $viewFactory)
    {
        $this->building = $building;
        $this->viewFactory = $viewFactory;
    }

    public function getBuilding()
    {
        return $this->building;
    }

    public function getViewFactory()
    {
        return $this->viewFactory;
    }

    public function indexAction()
    {
        $building = $this->getBuilding();
        $building->addLayer('blue');
        $building->addLayer();
        $building2 = $this->getBuilding(); //gets same instance
        $building2->addLayer('green');
        $building2->addLayer();
        $viewModel = $this->getViewFactory()->get('ViewModel', array('building'=>$building));
        return $viewModel;
    }
} 

<?php
foreach ($this->building->getBricks() as $key=>$layer)
{
    echo '<tr>';
    foreach ($layer as $brick)
    {
        echo '<td style="text-align:center; border:1px solid black">';
        echo $brick->getColor();
        echo '</td>';
    }
    echo '</tr>';
}
?>
And update the getControllerConfig method to accomodate the new constructor (adding the viewFactory)


    #Module.php
    public function getControllerConfig()
    {
        return array('factories' => array(
            'Building\Controller\Building' => function ($sm)
            {
                $building = $sm->getServiceLocator()->get('Building');
                $viewfactory = $sm->getServiceLocator()->get('ViewFactory');
                $controller = new Controller\BuildingController($building, $viewFactory);
                return $controller;
            }
        ));
    }


And we're done. Now you can go to http://localhost/building to see the building that you built. It should have 2 layers with random colors, and 2 layers with fixed colors. Try setting $shared to true in the BrickFactory closure and see what happens.

Hopefully this is enough to get you started writing your applications with injected dependencies using the ServiceManager as an inversion of control container, rather than a service locator. It will save you a lot of trouble in the long run.
You can get the source code used for this project at https://github.com/rwilson04/zf2-dependency-injection

Please leave me some comments, especially if you see something I did wrong, could do better, needs clarification, etc. For example, passing a model into the view? What was I thinking? 

(outdated) Using the ServiceManager as an Inversion of Control Container (Part 1)

Note: This post has been superseded. Check out the Part 1 and Part 2. The post you are reading now is outdated, and there is a much better way of doing this, in my opinion. 

Note about superseded posts: The new Part 1's changes mostly involve coding style and use of design patterns. In the outdated Part 2, I attempted to instantiate multiple instances of objects using the AbstractPluginManager. This worked for simple things, but got complicated when one of the non-shared instantiated instances had dependencies. The best I could work out involved creating factory factories, which created the required factories, which then created the model I wanted. Even without dependencies, the AbstractPluginManager method still required creating those extra factory classes for the sole purpose of creating objects, which leads to a plethora of files in bigger applications. Another deficiency of the outdated version is that it still has hard-coded dependencies in the form of type-hinting.

In Zend Framework 1, it was difficult to follow best practices when it came to writing testable code. Sure, you could make testable models, but once you need those models in a controller, what do you do? Zend Framework 2 makes it much easier. In this post, I'll cover the basics of injecting a model into a controller.

Motivation:

The main goal here is to be able to wire up and configure your application from the highest level possible. Constructor injection + inversion of control makes it easy to determine which classes are dependent on other classes. The Getting Started guide uses the ServiceManager in the Controller to pull in the model, which creates "soft dependencies", so you can't completely tell which classes depend on other ones unless you look at the code on the lower levels. For actual testable/maintainable code, avoid this as much as possible.

Prerequisites:
Begin by creating a new Building module, with a structure like the Album module in the Getting Started guide, and a route to /building so it is accessible. For the actual module code, let's start by adding the BuildingController with a Building model as a dependency, which we'll learn how to inject shortly.

<?php
#Building/src/Building/Controller/BuildingController.php
namespace Building\Controller;

use Building\Model\Building;

class BuildingController extends AbstractActionController
{
    protected $building;  

    public function __construct(Building $building)
    { 
        $this->building = $building;
    } 

    public function getBuilding()
    { 
        return $this->building;
    } 

    public function indexAction()
    { 
        $building = $this->getBuilding();
        $building->addLayer('red');
        $viewModel = $this->getViewModel(array('building'=>$building)); #we'll add this function later
        return $viewModel;
    } 
}

Zend Framework 2's ServiceManager allows you to programmatically configure your dependencies. You should already be at least vaguely familiar with this from the ZF2 Getting Started guide. The Application's ServiceManager is responsible for creating services. Internally, when tasked with creating a service with a particular name, "Building\Controller\Building" for example, it runs canCreate("Building\Controller\Building"), which checks all of your aggregated configs. So one of the places it checks by default is the array returned by the method getControllerConfig() in Module.php. This is where we can add the factory closure for the model that the controller needs access to.

    #Module.php
    public function getControllerConfig()
    { 
        return array('factories' => array(
            'Building\Controller\Building' => function ($sm)
            { 
                $building = $sm->getServiceLocator()->get('Building');
                $controller = new Controller\BuildingController($building);
                return $controller;
            } 
        ));
    }  
Creating controllers is actually handled by the ControllerManager, a (sub)subclass of the main ServiceManager, and $usePeeringServiceManagers is set to false, which is why here we need $sm->getServiceLocator()->get() instead of just $sm->get(); it needs to retrieve the main ServiceManager to have access to the rest of the application's services.

It is important to note that if the name of controller configuration (in this case Building\Controller\Building) is listed here, it cannot be defined somewhere else as well. For example, if it is in the $config['controllers']['invokables'] section in your module.config.php, the ServiceManager will try to 'invoke' it, that is, construct it with no arguments, and will fail. Check for this situation now.

Let's create the Building model as a simple class with no dependencies for now.

<?php
#Building/src/Building/Model/Building.php
namespace Building\Model;

class Building
{
    protected $colors = array("red", "brown", "black", "yellow", "orange", "purple", "green");

    public function addLayer($color=null)
    { 
        #add either a random color brick, or the color specified         $newBrickColor = ($color === null)?$this->colors[array_rand($this->colors)]:$color;
        echo "Added $newBrickColor brick";
    }
}

Since there are no dependencies or other required constructor arguments, we can define Building\Model\Building as an invokable in Module.php. The getServiceConfig() method is one of the aggregated configs that the ServiceManager checks to see which services it can create, and you should recognize it from the Getting Started guide.
    #Module.php
    public function getServiceConfig()
    { 
        return array(
            'invokables'=>array(
                'Building'=>'Building\Model\Building',
            ),
        );
    }  
That's it. Control has now been inverted. You are injecting a model into a controller using the ServiceManager, and all of your wiring and configuration is in one place. It is possible to separate the configuration into multiple files, which might be advisable once your wiring starts getting complicated. The factories can be their own classes which implement 'FactoryInterface' instead of closures defined in the config array.

If you want to try it out now, you'll need to replace the line in the controller
$viewModel = $this->getViewModel(array('building'=>$building));
with
$viewModel = new ViewModel(array('building'=>$building));
Then just go to http://localhost/building, and if you set your routing up, it will just be an empty page with the line echo'd in the indexAction. Hint: don't forget the getConfig() and getAutoloaderConfig() methods in your Module.php

Check out Part 2 to learn how to inject a dependency where each instance needs to be distinct and have its own configuration.

Please leave me some comments, especially if you see something I did wrong, could do better, needs clarification, etc.