Robert Basic's blog

Working with custom view helpers in Zend Framework 2

by Robert Basic on September 11th, 2012

Zend Framework hit a big milestone as version 2 was released last week. Sadly, I didn't have time to contribute to it, or even to poke around it much. I decided to slowly, as time permits, port this blog to ZF2; it should be a good enough learning playground.

I took the skeleton application, made it even skinnier by throwing out some (for me) unneeded parts and just put it all besides my old ZF1 code. Note: I think it could be possible to have a ZF1 and a ZF2 app run side by side, something like Stefan did for Symfony1 and Symfony2. Need to investigate on this. The first problem I ran into was using custom view helpers, especially view helpers that are more general and don't fit into one specific module. Where to put the code? How to access them in views? The second problem was how to access the service manager from a view helper? And the third problem was how to tell the helper to use a specific value when inside a specific module?

Custom view helpers

As I found out, custom view helpers can live in different places (at least two) - in the vendor/ directory, or in the module/ directory as a part of a module. Both ways are probably good solutions, but for me it's way easier to use the custom view helpers when they are "packaged" as a module:

On the image you can see I put the helpers in a module called Hex. The module.config.php file is something like:

<?php
return array(
    'view_helpers' => array(
        'invokables' => array(
            'customHelper' => 'Hex\View\Helper\CustomHelper',
            // more helpers here ...
        )
    )
);
The Module.php file is completely basic, it just has the Module class, implementing the getConfig() and the getAutoloaderConfig() methods. Include the module in the application's main configuration file (config/application.config.php) and now the customHelper can be simply called from the view files as:
<php echo $this--->customHelper(); ?>

Service manager in a view helper

To access the service manager in a view helper, it needs to implement the ServiceLocatorAwareInterface and two methods, setServiceLocator() and getServiceLocator(). In code, this would be something like:

<php
namespace Hex\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomHelper extends AbstractHelper implements ServiceLocatorAwareInterface
{
    /**
     * Set the service locator.
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return CustomHelper
     */
    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
        return $this;
    }
    /**
     * Get the service locator.
     *
     * @return \Zend\ServiceManager\ServiceLocatorInterface
     */
    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }
    public function __invoke()
    {
        $serviceLocator = $this->getServiceLocator();
        // use it at will ...
    }
}
Now, if you call getServiceLocator(), you'll actually get the Zend\View\HelperPluginManager which gives access to other view helpers. If you want to access the "application wide" service locator, you need to call getServiceLocator() again on the HelperPluginManager. Confused yet?
// first one gives access to other view helpers
$helperPluginManager = $this->getServiceLocator();
// the second one gives access to... other things.
$serviceManager = $helperPluginManager->getServiceLocator();

Module specific values in view helpers

The problem (a bit simplified): how to tell the view helper to show "Welcome to my site!" on all routes/modules, except for the blog module, where it should show "Welcome to my blog!". This one was the trickiest, as it can be solved in way too many ways. I first tried 2-3 different approaches, but they either didn't work at all, or were just plain ugly hacky solutions. The final solution came to me when I answered a different question: what would be the easiest way to unit test this code? First, the custom view helper:

<?php
namespace Hex\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Greeter extends AbstractHelper implements ServiceLocatorAwareInterface
{
    protected $message = null;
    /**
     * Set the service locator.
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return CustomHelper
     */
    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
        return $this;
    }
    /**
     * Get the service locator.
     *
     * @return \Zend\ServiceManager\ServiceLocatorInterface
     */
    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }
    public function setMessage($message)
    {
        $this->message = $message;
        return $this;
    }
    public function getMessage()
    {
        if ($this->message === null) {
            // for example, get the default value from app config
            $sm = $this->getServiceLocator()->getServiceLocator();
            $config = $sm->get('application')->getConfig();
            $this->setMessage($config['message']);
        }
        return $this->message;
    }
    public function __invoke()
    {
        $message = $this->getMessage();
        return $message;
    }
And the blog module specific part, where we hook to the "preDispatch" event:
<?php
namespace Blog;
class Module
{
    public function onBootstrap($e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $eventManager->attach('dispatch', array($this, 'preDispatch'), 100);
    }
    public function preDispatch($e)
    {
        $matchedRoute = $e->getRouteMatch()->getMatchedRouteName();
        // check for the matched route
        // and change the greeter message if needed
        if ($matchedRoute == 'blog') {
            $moduleConfig = $this->getConfig();
            $sm = $e->getApplication()->getServiceManager();
            $helper = $sm->get('viewhelpermanager')->get('greeter');
            $helper->setMessage($moduleConfig['message']);
        }
    }
And that would be it. I think.

Happy hackin'!

Tags: zend framework 2, custom, php, view helpers.
Categories: Development, Programming, Software.