While there are several bundles that would make integrating Twitter Bootstrap into Symfony as easy as including a new bundle with Composer, I like to learn how things work behind the scenes. In this article I will attempt to show you how this is done when you want to use Twitter Bootstrap’s form styles with your Symfony forms. I am assuming you are familiar with Symfony’s Form components and Twitter Bootstrap in general.
Twig’s form_row block is responsible for rendering the label, the input and any errors the form may have, but where does the template that defines this block come from? To answer that question we need to look deep into Symfony’s source:
vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
Every part of the form renders from blocks defined somewhere inside this file. Since the form_row is responsible for the layout, lets search for the form_row block inside this file.
{% block form_row %}
{% spaceless %}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
We found it! But wait, any decent developer out there should know by now that we don’t want to overwrite this default file that’s part of Symfony’s core. If you do that, when you upgrade Symfony all your changes will be gone. Instead, let’s make a new file in the app/Resources/views directory and override the default look to work with Twitter Bootstrap.
{% block form_row %}
{% spaceless %}
<div class="control-group">
{{ form_label(form) }}
<div class="controls">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{% endblock form_row %}
This is the minimum configuration you need to get your Symfony forms working with Twitter Bootstrap. In order for Symfony to know we want to override the look of the forms, we need to tell it to use our new forms file. Open up the config.yml file and add the pointer to your new forms layout.
# Twig Configuration
twig:
form:
resources:
- "::forms.html.twig"
We are now in control of how the form will be rendered. If you browse to your form and inspect an element you will see that it now uses our new layout. If you submit your form blank, you will noticed that the errors are still pretty ugly. Let’s fix that.
Since we don’t know where the errors are coming from, let’s search the original layout file (form_div_layout.html.twig) for the word form_errors and see if we find anything.
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<ul>
{% for error in errors %}
<li>{{
error.messagePluralization is null
? error.messageTemplate|trans(error.messageParameters, 'validators')
: error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators')
}}</li>
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock form_errors %}
Copy the block to our new file and let’s modify it to play well with Twitter Bootstrap:
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<span class="help-inline">
{% for error in errors %}
{{
error.messagePluralization is null
? error.messageTemplate|trans(error.messageParameters, 'validators')
: error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators')
}}
{% endfor %}
</span>
{% endif %}
{% endspaceless %}
{% endblock form_errors %}
As you can see we got rid of those ugly unordered lists and replaced them with Twitter Bootstrap’s help-inline span. Things are starting to look much better, but wouldn’t it be nice if we could highlight the fields when something goes wrong? You guessed it, Twitter Bootstrap provides just this.
Let’s go back to our first block, the form_row block. And let’s add the error class to the controls-group:
{% block form_row %}
{% spaceless %}
<div class="control-group error">
{{ form_label(form) }}
<div class="controls">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{% endblock form_row %}
Ok, that’s almost what we want… but shouldn’t we highlight only the fields that actually have errors on them? Symfony makes this easy. In this block we have access to the errors variable. In fact there are a few other variables that we have access to as well, that are beyond the scope of this article. To only add the error class when we need it all we have to do is test for such:
{% block form_row %}
{% spaceless %}
<div class="control-group {{ errors|length > 0 ? 'error' : '' }}">
{{ form_label(form) }}
<div class="controls">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{% endblock form_row %}
With this our forms are looking much better. As you can see modifying our forms is as easy as creating a file to selectively override the default block code found in form_div_layout.html.twig. I invite you to continue to play with this file and see what other customization you can do. For now, we have the basis for a Twitter Bootstrap form in place and with a little more work we could be 100% bootstrapped!