Search code examples
symfonytwigsymfony-forms

Find out / customize Symfony Forms Twig block name


I want to override how parts of my form is rendered, but I always forget to which twig block names symfony forms renders. If the block is not found, I get no feedback why not was not found, and how it should have been called.

Example:

class SetPasswordType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('plainPassword', PasswordType::class);
        $builder->add('repeatPlainPassword', PasswordType::class);
    }
}

Three essential questions:

  • How does symfony forms determine the twig block name(s) for the SetPasswordType?
  • How can I look this up?
  • How can I customize this?

Solution

  • For this specific type, the twig widget block name is set_password_widget.

    Override it in your form theme like this:

    {% block set_password_widget %}
        <div class="row">
            <div class="col">
                {{ form_row(form.plainPassword) }}
            </div>
            <div class="col">
                {{ form_row(form.repeatPlainPassword) }}
            </div>
        </div>
    {% endblock %}
    

    How to override a block

    • Determine the block prefix, which by default depends on the class name. For SetPasswordType, it is set_password
    • Decide which block you want to render. If you want to change the layout, likely you will override the row block. If you want to change the input, likely you will override the widget block. Refer to the documentation: https://symfony.com/doc/current/form/form_themes.html#form-fragment-naming
    • Join the block prefix and the block name together. For the widget block of the SetPasswordType, you want to override set_password_widget.
    • Place the twig block in your form theme.

    Troubleshoot if overriding fails

    • Check that the twig block is in a file configured in twig.form_themes or otherwise as a form theme included.
    • Inspect $blockPrefixes in Symfony\Component\Form\Extension\Core\Type\BaseType::buildView to see which block prefixes were determined. The last entry in the array will be looked for first.
    • Inspect $blockNameHierarchy in Symfony\Component\Form\FormRenderer::searchAndRenderBlock to see which specific blocks are looked for. The last entry in the array will be looked for first.

    Detailed walkthrough

    The names of the blocks are build up in Symfony\Component\Form\Extension\Core\Type\BaseType::buildView, within the $blockPrefixes variable. $blockPrefixes defines the following priority (first mentioned = highest priority. It is stored in reverse!):

    • the unique block name (built up hierarchically based on property names).
    • the block prefix, if one is configured
    • the name of the type (if inheriting from AbstractType, the name is generated in AbstractType::getBlockPrefix())
    • the name of all parent types (following the chain of the FormTypeInterface::getParent() method).

    For example, it could look like this:

    $blockPrefixes = [
      0 => "form", # because `AbstractType::getParent()` returns `FormType`
      1 => "set_password", # our type, name resolved automatically depending on class name 
    
      ## at this position, a block prefix would be included if one is configured 
    
      2 => "_set_password" # the hierarchical unique name, prefixed with `_`. for the plainPassword field, this value would be "searchAndRenderBlock". 
    ];
    

    When the form is rendered by twig, the Symfony\Component\Form\FormRenderer::searchAndRenderBlock is called with a respective $blockNameSuffix (for example widget or row). The $blockNameHierarchy is created by suffixing each potential prefix with the $blockNameSuffix.

    For $blockNameSuffix == "row" it would then look like this:

    $blockNameHierarchy = [
      0 => "form_widget",
      1 => "set_password_widget",
      2 => "_set_password_widget"];
    

    These blocks are looked for, and the one with the highest priority which can be found is chosen. The later in the array, the higher the priority.

    Customize block prefixes

    You can customize the prefixes like this:

    class SetPasswordType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('plainPassword', PasswordType::class, ['mapped' => false]);
            $builder->add('repeatPlainPassword', PasswordType::class, ['mapped' => false]);
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefault('block_prefix', 'my_custom_prefix');
            $resolver->setDefault('block_name', 'my_custom_name');
        }
    }
    

    This will result in

    $blockPrefixes = [
      0 => "form", 
      1 => "set_password", # this will stay the same as its resolved depending on type name
    
      2 => "my_custom_prefix", # as per our configuration, we now have a custom prefix
    
      3 => "_my_custom_name" # the custom name will be used to determine the hierarchical name
    ];