Jan 15 2012

Integrating Zend Framework 1 and Pimple

This post will describe a way to integrate Zend Framework 1 and Pimple. A complete working version of the code is available on github.

Thankfully, Zend Framework 2 features its own Dependency Injection Container. Happy days. Still, if you're not prepared to wait, you may find this useful.

What is Pimple?

Pimple is a small Dependency Injection Container for PHP 5.3 that consists of just one file and one class (about 50 lines of code).

If you aren't too familiar with Dependency Injection and why it's useful, check out these slides by Fabien Potencier.

Why bother?

It allows you to mock dependencies in your controllers within your controller tests. In this example, the Doctrine 2 entity manager is added into the container. If you're just after Zend Framework 1 and Doctrine 2 integration, you will certainly find this post from the Mayflower blog helpful.

Add a bootstrap resource plugin for the container

The bootstrap resource plugin in this example adds the entity manager closure into Pimple. The init method returns the container, thus it can be retrieved from within a controller.

More information on resource plugins

// library/Example/Application/Resource/Container.php
namespace Example\Application\Resource;

use Zend_Application_Resource_ResourceAbstract,
    Doctrine\ORM\EntityManager,
    Doctrine\ORM\Tools\Setup;

class Container extends Zend_Application_Resource_ResourceAbstract
{
    public $_explicitType = 'container';

    protected $container;

    public function __construct($options = null, $container = null)
    {
        parent::__construct($options);

        $this->container = $container;
    }

    public function init() 
    {
        $options = $this->getBootstrap()->getOptions();
        $db = $options['db'];

        $this->container['entityManager'] = $this->container->share(function ($c) use ($db) {
            $isDevMode = APPLICATION_ENV == 'development';

            $config = Setup::createAnnotationMetadataConfiguration(array(APPLICATION_PATH . '/models'), $isDevMode);

            return EntityManager::create($db, $config);
        });

        return $this->container;
    }
}

Resource plugins can be added from the application configuration. However, it's important that it is instantiated outside of the configuration to allow injection of the Pimple container. This is an extremely important step for testing.

// public/index.php

// ...

$resource = new \Example\Application\Resource\Container(null, new Pimple);
$application->getBootstrap()->registerPluginResource($resource);

$application->bootstrap()
            ->run();

Create the controller

Within the action, the container is retrieved from the bootstrap.

// application/controllers/IndexController.php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $bootstrap = $this->getInvokeArg('bootstrap');
        $container = $bootstrap->getResource('container');

        $em = $container['entityManager'];

        $this->view->items = $em->getRepository('Application_Model_Item')->findAll();
    }
}

Add a test for the controller

I've used the PHPUnit mock library for the example. Mockery is better though. Didn't want to throw another library into the mix!

// tests/application/controllers/IndexController.php
class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    protected $container;

    protected function setUp()
    {
        $this->bootstrap = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
        $this->container = new Pimple;
        $resource = new Example\Application\Resource\Container(null, $this->container);
        $this->bootstrap->getBootstrap()->registerPluginResource($resource);
        parent::setUp();
    }

    public function testIndexAction()
    {
        $items = array(
            $this->createItem('test1', 'test1value'),
            $this->createItem('test2', 'test2value'),
        );

        // mock the entity repository
        $repo = $this->getMockBuilder('Doctrine\ORM\EntityRepository')
            ->disableOriginalConstructor()
            ->getMock();

        $repo->expects($this->once())
            ->method('findAll')
            ->will($this->returnValue($items));

        // mock the entity manager
        $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
            ->disableOriginalConstructor()
            ->getMock();

        $em->expects($this->once())
            ->method('getRepository')
            ->with($this->equalTo('Application_Model_Item'))
            ->will($this->returnValue($repo));

        // replace the entity manager closure in the container
        $this->container['entityManager'] = $em;

        $this->dispatch('/');

        $this->assertQueryContentContains("h1", "Items");
        $this->assertQueryContentContains('ul li', 'test1: test1value');
        $this->assertQueryContentContains('ul li', 'test2: test2value');
    }

    protected function createItem($name, $value)
    {
        $item = new Application_Model_Item;
        $item->setName($name);
        $item->setValue($value);
        return $item;
    }
}

Improvements

  • Populate Pimple outside of the bootstrap resource plugin. This step allows you to have an empty container during testing (no forgetting to replace objects in the container).
  • Allow easier access to the container via an action helper or by extending Zend_Controller_Action.
comments powered by Disqus

Info

I'm Ade Slade, a PHP web developer.