Wednesday, September 11, 2013

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

In 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? Here is the Brick class:
<?php
#module/Building/src/Building/Model/Brick.php
namespace Building\Model;

class Brick
{
    protected $_color;
    protected $_randomColors = array("red", "brown", "black", "yellow",
        "orange", "purple", "green");

    public function __construct($color)
    {
        $this->_color = ($color===null)?"default":$color;
    }

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

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

    public function getRandomColor()
    {
        return $this->_randomColors[array_rand($this->_randomColors)];
    }
}


Now the Building model needs to be able to get multiple unique instances of Brick. To accomplish this, we'll need a basic understanding closures and capturing state. If we follow the same pattern as before, and add Brick as a dependency in Building, we will only have access to a single instance of Brick. Additionally, we will be unable to provide any runtime dependencies, such as the $color parameter in Brick's construction.

<?php
#module/Building/src/Building/Model/Building.php (wrong)
namespace Building\Model;

class Building
{
    protected $_brick;

    public function __construct($brick)
    {
        $this->_brick = $brick;
    }

    public function getBrick()
    {
        return $this->_brick;
    }

In order to have multiple Bricks instantiated from within Building, we'll need to pass in the ability for Building to do so on its own. This can be done by passing in a BrickFactory. The factory is another closure, so we will have a closure within a closure. Notice that the runtime parameter $color is able to be passed in for the Brick construction

    #module/Building/Module.php excerpt
    public function getServiceConfig()
    {
        return array(
            'factories'=>array(
                'BrickFactory'=>function($sm)
                {
                    $factory = function ($color=null)
                    {
                        $brick = new Model\Brick($color);
                        return $brick;
                    };
                    return $factory;
                },
                //...

The $factory in this BrickFactory service doesn't need access to the ServiceManager in this example, but to imagine the Brick class having a BrickMapper dependency. If you are following along, don't copy this into your code.

    #module/Building/Module.php excerpt (aside)
    public function getServiceConfig()
    {
        return array(
            'factories'=>array(
                'BrickFactory'=>function($sm)
                {
                    $factory = function ($color=null) use ($sm)
                    {
                        $mapper = $sm->get('BrickMapper');
                        $brick = new Model\Brick($mapper, $color);
                        return $brick;
                    };
                    return $factory;
                },
                'BrickMapper'=>function($sm)
                {
                    $factory = $sm->get('BrickFactory');
                    $mapper = new Model\BrickMapper($factory);
                    return $mapper;
                },
                'Brick'=>function($sm)
                {
                    $factory = $sm->get('BrickFactory');
                    $model = $factory->__invoke();
                    return $model;
                },
                //...
Luckily, doing it this way does not create circular dependencies, even though the mapper uses the factory, and the factory uses the mapper. Notice also that if we needed a single Brick model in some other class, we can create a brick for the ServiceManager using the BrickFactory, albeit without runtime construction parameters.

The use keyword is what captures the ServiceManager for use later, when the factory is invoked. Without it, the factory would be unable to create the BrickMapper, because the scope of the closure is limited to itself.
Anyway, back to creating Bricks. We can now modify the Building class to use the BrickFactory to create new Bricks. The factory closure needs be called, or 'invoked'. This can be done with either $factory() or $factory->__invoke(). I prefer the latter, as it is slightly less mysterious what is going on. The parameters inside the parenthesis are passed to the $factory closure.

    #module/Building/src/Building/Model/Building.php excerpt

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

    public function getNewBrick($color=null)
    {
        $factory = $this->_brickFactory;
        $brick = $factory->__invoke($color);
        return $brick;
    }

    public function addLayer($color=null)
    {
        $layer = array();
        for ($i=1; $i<=6; $i++)
        {
            $brick = $this->getNewBrick();
            if ($color === null)
            {
                //constructed with the default color and then initialized
                $brick = $this->getNewBrick();
                $brick->setColor($brick->getRandomColor());
            }
            else
            {
                //constructed fully initialized with runtime 
                //  parameter (usually preferred)
                $brick = $this->getNewBrick($color);
            }
            $layer[] = $brick;
        }
        $this->_bricks[]=$layer;
    }

What we've just done can be difficult to comprehend if closures are new to you. They are a tricky concept, but once understood, can be used to create such elegant code. Leave me a comment if this needs elaboration. For now, we are done with the conceptual stuff. Continue reading to get everything running.
Next, we need to configure and inject this dependency. While we're here, lets do the same thing for the ViewModel that the controller needs.

    #module/Building/Module.php excerpt
    public function getServiceConfig()
    {
        return array(
            'factories'=>array(
                //...
                'Building'=>function($sm)
                {
                    $factory = $sm->get('BrickFactory');
                    $building = new Model\Building($factory);
                    return $building;
                },
                'ViewFactory'=>function($sm)
                {
                    $factory = function($variables=null, $options=null)
                    {
                        $viewModel = new \Zend\View\Model\ViewModel($variables,
                            $options);
                        return $viewModel;
                    };
                    return $factory;
                },
                //...
And finally, add those dependencies to the constructors in their respective classes
    #module/Building/Module.php excerpt
    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;
            }
        ));
    }
    #module/Building/src/Building/Controller/BuildingController.php excerpt
    public function getViewModel($variables = null, $options = null)
    {
        return $this->_viewFactory->__invoke($variables, $options);
    }

    public function indexAction()
    {
        $building = $this->getBuilding();
        $building->addLayer('blue');
        $building->addLayer();
        $building2 = $this->getBuilding(); //gets same instance
        $building2->addLayer('green');
        $building2->addLayer();
        $viewModel = $this->getViewModel(array('building'=>$building));
        return $viewModel;
    }
<?php
#module/Building/src/Building/Model/Building.php
namespace Building\Model;

class Building
{
    protected $_brickFactory;
    protected $_bricks;

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

    public function getNewBrick($color=null)
    {
        $factory = $this->_brickFactory;
        $brick = $factory->__invoke($color);
        return $brick;
    }

    public function addLayer($color=null)
    {
        $layer = array();
        for ($i=1; $i<=6; $i++)
        {
            $brick = $this->getNewBrick();
            if ($color === null)
            {
                $brick->setColor($brick->getRandomColor());
            }
            else
            {
                $brick->setColor($color);
            }
            $layer[] = $brick;
        }
        $this->_bricks[]=$layer;
    }

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

}
<?php #module/Building/view/building/building/index.pthml ?>
<table >
<?php
foreach ($this->building->getBricks() as $key=>$layer)
{
    echo '<tr>';
    foreach ($layer as $brick)
    {
        echo '<td style="text-align:center; border:1px solid black; '.
            'background-color:' . $brick->getColor() . '">';
        echo $brick->getColor();
        //echo spl_object_hash($brick);
        echo '</td>';
    }
    echo '</tr>';
}
?>

Now you can go to http://localhost/building to see that everything works as expected. You should see 4 layers: a green, a blue, and 2 with random colored bricks. Go to https://github.com/rwilson04/zf2-dependency-injection/tree/part2 for complete code.
For production, you might want to convert the closures in the 'factories' key to classes, as suggested in this article.
I'm still trying to figure out how to include initializers, and have them work on the objects created by the closures. If you have a solution, let me know.
Another thing to consider would be type-hinting. For anything that is swappable, you would define an interface and add that to the method definitions. For other types, I'm not sure yet whether including type-hinting is the right way to go.
Thanks for reading. I hope this helped. Leave me a comment, especially if there is something missing or something that needs clarification.

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

Intro:

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, and swapping classes with mocks or stubs is a breeze.

Prerequisites:
Begin by creating a new Buildingmodule, with its directory structure, similar to creating the Album module in the Getting Started guide.

To create objects, first let's take a look at what we are trying to get away from.

    $building = new \Building\Model\Building();

This is bad. At least in a controller, or another model. No more new anything (except maybe Exceptions...), no more hard-coded class names. Dependencies don't belong at this level of the code. Zend Framework 2's ServiceManager allows you to programmatically configure your dependencies. You should already be at least vaguely familiar with higher level dependency configuration from the ZF2 Getting Started guide, as this is how the AlbumController is configured, though it does a lot of stuff behind the scenes.

    $sm = $this->getServiceLocator();
    $building  = $sm->get('Building');

This approach is better, but you still don't want to put this in the controller. You should avoid pulling in classes using the ServiceManager anywhere except factories. Put another way, in general, $sm->get() does not belong in a controller or model, but does belong in Module.php's factories array, which I'll demonstrate further down. A class full of objects created this way has a lot of "soft dependencies", which are difficult to document and keep track of. Instead, to 'inject' a dependency into a class, it should be a parameter in the construction. Let's start by creating the BuildingController with a Building model as a dependency, which we'll learn how to inject shortly. I usually store dependencies as instance variables during construction and access them when needed with 'getters'. In this example, we are still instantiating a new ViewModel from within the controller. We'll fix this in Part 2, where we learn how to use the ServiceManager to create objects with runtime dependencies (such as the $variables parameter in the ViewModel construction)

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

use Zend\Mvc\Controller\AbstractActionController;

class BuildingController extends AbstractActionController
{
    protected $_building;

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

    protected function _getBuilding()
    {
        return $this->_building;
    }
    protected function _getViewModel($variables=null)
    {
        #we'll fix this later
        return new \Zend\View\Model\ViewModel($variables);
    }
    public function indexAction()
    {
        $building = $this->_getBuilding();
        $building->addLayer('blue');
        $building->addLayer('red');
        $viewModel = $this->_getViewModel(array('building'=>$building));
        return $viewModel;
    }
}

The Application's ServiceManager is responsible for creating services (the model and controller are both 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 config arrays. One of the places it checks by default is the array returned by the method getControllerConfig() in Module.php, if it exists. This is where we can add the factory closure for the model that the controller needs access to.

    #module/Building/Module.php excerpt
    public function getControllerConfig()
    {
        return array('factories' => array(
            'Building\Controller\Building' => function ($sm)
            {
                $building = $sm->getServiceLocator()->get('Building');
                $controller = new Controller\BuildingController(
                    $building, $viewFactory);
                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 we need $sm->getServiceLocator()->get() here instead of just $sm->get(); it needs to retrieve the main ServiceManager to have access to the rest of the application's services.

In this controller, the constructor triggers the instantiation of the Building model. The model isn't actually created until the controller's constructor runs, since it is not a model being passed in, but a factory. The factory is a closure, a function without a name which can be saved as a variable. The ServiceManager sets services as 'shared' by default, meaning that the first time it is referenced, it calls the closure creating the object, and every time after that it simply returns the already instantiated object.

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. If you are following along with your code, check if this is the case now, and fix it if necessary. I'll wait.

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

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

class Building
{
    protected $_bricks=array();

    public function addLayer($color)
    {
        $bricks = array($color, $color, $color);
        $this->_bricks[] = $bricks;
    }

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

}

Because 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/Building/Module.php excerpt
    public function getServiceConfig()
    {
        return array(
            'invokables'=>array(
                'Building'=>'Building\Model\Building',
            ),
        );
    }


ZF2 provides multiple ways of doing a lot of things. It is possible, for example, 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.

But, we are done for now. Control has now been inverted. We are injecting a dependency (Building) into a controller using the ServiceManager, and all of our wiring and configuration is in one place, Module.php.

If you want to test it out now and get something running, read the rest of this post. Otherwise, check out Part 2 to learn how to inject a dependency where each instance needs to be distinct and have its own configuration, e.g. using a $model->findAll() or similar.

You'll need to create a view

 
<?php
#module/Buidling/view/building/building/index.phtml
?>
<table >
<?php
foreach ($this->building->getBricks() as $key=>$layer)
{
    echo '<tr>';
    foreach ($layer as $brick)
    {
        echo '<td style="text-align:center; border:1px solid black;'.
            ' background-color:' . $brick . ';">';
        echo $brick;
        echo '</td>';
    }
    echo '</tr>';
}
?>
</table>

And then be sure to set up a route to /building so it is accessible.
 
<?php
#module/Building/config/module.config.php
return array(
    'router' => array(
        'routes' => array(
            'building' => array(
                'type'    => 'segment',
                'options' => array(
                    'route'    => '/building[/:action]',
                    'constraints' => array(
                        'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                    ),
                    'defaults' => array(
                        'controller' => 'Building\Controller\Building',
                        'action'     => 'index',
                    ),
                ),
            ),
        ),
    ),
    'view_manager' => array(
        'template_path_stack' => array(
            'building' => __DIR__ . '/../view',
        ),
    ),
);

Don't forget the getConfig() and getAutoloaderConfig() methods in your Module.php.

    
    #module/Building/Module.php excerpt
    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\ClassMapAutoloader' => array(
                __DIR__ . '/autoload_classmap.php',
            ),
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

Finally, just go to http://localhost/building, and you should see a simple empty page with the layers we added.
Check out https://github.com/rwilson04/zf2-dependency-injection/tree/part1 for the code.

Here's the link to Part 2 again.

Comments are welcome.