Pragmatism in the real world

Using ZF2 Forms with Twig

Following on from looking at how to integrate Zend Framework 2 forms into Slim Framework, let’s look at the changes required if you also happen to want to use Twig.

When it comes to rendering the form, we would want our template to look like this:

    <form method="POST" role="form">
        <div class="form-group">
            {{ formRow(form.get('email')) }}
        </div>
        {{ formElement(form.get('submit')) }}
    </form>

The ZF2 view helpers, formRow and formElement now look like Twig functions, however we don’t want to have to rewrite all our ZF2 view helpers into Twig. Fortunately, Twig supports the concept of a undefined function callback is called whenever Twig encounters a function that it doesn’t know how to call. We can use this to proxy through to the Zend\View system and get it to render the ZF2 view helpers.

Let’s look at how we do this.

Starting with the work in the previous article, we merely need to change the view layer. That is, we don’t need a custom PHP view (\RKA\View), but instead use the Slim-Views Twig component.

To add Twig to a Slim project we update composer.json and add "twig/twig": "1.16.*" and "slim/views": "0.1.*" to the require section and then run composer update.

We then update index.php to add the view class when instantiating the Slim and configure it.:

$app = new \Slim\Slim([
    'view' => new \Slim\Views\Twig()
]);

// Configure Twig
$view = $app->view();
$view->parserOptions = [
    'debug' => true,
    'cache' => false,
];
$view->parserExtensions = array(
    new \Slim\Views\TwigExtension(),
);

Now, whenever we call $app->render(), Twig will be used. The view templates have a .twig extension, so we rename home.php to home.twig and it’s rendered like this:

$app->render('home.twig', [
    'form' => $form
]);

To integrate the ZF2 Form view helpers without having to rewrite them all as Twig extensions, we just need to register an undefined callback function that uses Zend Framework 2’s PhpRenderer to render the view helper.

This is done with a few lines in index.php:

$viewHelperManager = $app->serviceManager->get('ViewHelperManager');
$renderer = new \Zend\View\Renderer\PhpRenderer();
$renderer->setHelperPluginManager($viewHelperManager);

$view->getInstance()->registerUndefinedFunctionCallback(
    function ($name) use ($viewHelperManager, $renderer) {
        if (!$viewHelperManager->has($name)) {
            return false;
        }

        $callable = [$renderer->plugin($name), '__invoke'];
        $options  = ['is_safe' => ['html']];
        return new \Twig_SimpleFunction(null, $callable, $options);
    }
);

Let’s break this down.

Firstly, we create a PhpRenderer and set the helper plugin manager to ViewHelperManager that’s already defined in the service manager.

We can access the underlying Twig_Environment object using $view->getInstance() and then call registerUndefinedFunctionCallback with a closure for our callback.

The callback code is this bit:

    function ($name) use ($viewHelperManager, $renderer) {
        if (!$viewHelperManager->has($name)) {
            return false;
        }

        $callable = [$renderer->plugin($name), '__invoke'];
        $options  = ['is_safe' => ['html']];
        return new \Twig_SimpleFunction(null, $callable, $options);
    }

Within the callback we check the ViewHelperManager to see if we have a view helper that we can call. If we don’t then we return false so that Twig can try a difference undefined function callback in its list.

If we do know about this view helper, then we instantiate a Twig_SimpleFunction that will call the __invoke method of the view helper which we retrieve via the plugin method of the renderer. Note that we also tell Twig that our view helper is safe to use with HTML so it doesn’t escape the HTML tags created by the view helper.

That’s all there is to it.

The code that’s shown here is on GitHub in the twig branch of the slim-zendform project, so you can download it and play around yourself.