2
votes

I want to define a new twig function "form_field" which renders form_label, form_widget and form_errors. I don't want to override form_row because I already did that for a different purpose: form_row also wraps <div/> container required for my layout. Specifically, I add bootstrap classes.

Why do I need this? Normally I use form_row but I have lots of form rows for which I want to place two ore more fields in the same line (e.g. "first name" and "last name"). In those cases I define my wrapping <div/> manually, but I don't want to write the three function calls (form_label, form_widget and form_errors) everytime.

I tried to specify a custom function, but I get an exception when calling the twig functions there. For this solution I need to know, how to call Twig functions from a custom function.

My second attempt was to adapt Symfony's solution for its own form helpers. I found this line in vendor/symfony/symfony/src/Symfony/Bridge/Twig/Extension/FormExtension.php:

new \Twig_SimpleFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),

Looks like a function which actually just renders a block. When I adapted that line in my AppBundle, an exception was thrown in which Symfony claimed that a block named "form_field" is undefined. However, I defined this block in my custom form template:

{% use 'bootstrap_3_horizontal_layout.html.twig' %}

{%- block form_field -%}
    {{- form_label(form) -}}
    {{- form_widget(form) -}}
    {{- form_errors(form) -}}
{%- endblock form_field -%}
1
Since in Symfony's form theming there is some magic hapening, the solution may depend on how is your form built. Are first name and last name fields added to form builder as single, but compound-type field?Jakub Matczak
Those are two single fields. I would add them this way: <div ...>form_field(form.firstName)</div><div ...>form_field(form.lastName)</div>fishbone

1 Answers

1
votes

Declare a twig template Form/form_field.html.twig:

{%- block form_field -%}
    {{- form_label(form, label) -}}
    {{- form_widget(form) -}}
    {{- form_errors(form) -}}
{%- endblock form_field -%}

Then I used the renderBlock method in my Twig custom function. This is my twig extension file as described here:

namespace AppBundle\Twig;

class AppExtension extends \Twig_Extension
{
    protected $environment;
    private $template;

    public function __construct(\Twig_Environment $env)
    {
        $this->environment = $env;
    }


    public function getFunctions()
    {
        return array(
            'form_field' => new \Twig_SimpleFunction('form_field', array($this, 'formField'), array(
                'is_safe' => array('html')
            ))
        );
    }

    public function formField($form, $label = null)
    {
        $this->template = $this->environment->loadTemplate( '::Form/form_field.html.twig' );

        return $this->template->renderBlock('form_field', array(
            'form' => $form,
            'label' => $label
        ));
    }

    /**
     * Returns the name of the extension.
     *
     * @return string The extension name
     */
    public function getName()
    {
        return 'app_extension';
    }
}

I configured the DI container to pass the Twig_Environment for the AppExtension service:

services:
    app.twig_extension:
        class: AppBundle\Twig\AppExtension
        arguments:
            - '@twig'
        tags:
            - { name: twig.extension }