Buy

Symfony 4 Forms: Build, Render & Conquer!

0%
Buy

Form Rendering Functions: form_*

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

To render the form, we're using a few form functions: one that makes the form start tag, one that makes the end tag and one that renders all the fields, labels and errors inside.

This was easy to set up. The problem is that we have almost no control over the HTML markup that's used! Sure, we were able to activate a "form theme" that told it to use Bootstrap-friendly markup. But, what if you need more control?

This is probably the hardest part of Symfony's form system. But don't worry: we're going to learn several different strategies to help you get the markup you need... without going crazy... probably.

The Form Rendering Functions

Go to your other tab and Google for "Symfony form rendering functions" to find a page that talks all about the functions we're using and a few others.

First, form_start(), yes, this does just render the form start tag, which might seem kind of silly, but it can come in handy when you add a file upload field to your form: it automatically add the enctype attribute.

Oh, but notice: form_start() has a second argument: an array of variables that can be passed to customize it. Apparently you can pass method to change the method attribute or attr to add any other attributes to the form tag - like a class.

Next: find form_end(). This one seems even sillier because it literally prints... yep! The form closing tag! But, it has a hidden superpower: it also renders any fields that we forgot to render. Now, that might not make sense yet because this magic form_widget() function seems to be rendering everything automatically. But, in a moment, we'll render the fields one-by-one. When we do that, if we've forgotten to render any of the fields, form_end() will render them for us... and then the closing tag.

That still may not seem like a good feature... and, in many ways, it's not! In reality, the purpose of this is not so that we can be lazy and form_end() will save us. Nope - the true purpose is that form_end() will render any hidden fields automatically, without us needing to even think about them. Most importantly, it will render your form's CSRF token

CSRF Token

Inspect element near the bottom of the form. Woh! Without us doing anything, we have a hidden input tag called _token. This is a CSRF token and it was automatically added by Symfony. And, even cooler, when we submit, Symfony automatically validates it.

Without even knowing it, all of our forms are protected from CSRF attacks.

form_widget and form_row()

Back to the form rendering goodness! To print the form fields themselves, the easiest way is to call form_widget() and pass the entire form. But, if you need a little bit more control, instead of form_widget(), you can call form_row() and render each field individually. For example, articleForm.title. Copy that and paste it three more times. Render articleForm.content, articleForm.publishedAt and articleForm.author.

... lines 1 - 5
{{ form_start(articleForm) }}
{{ form_row(articleForm.title) }}
{{ form_row(articleForm.author) }}
{{ form_row(articleForm.content) }}
{{ form_row(articleForm.publishedAt) }}
... line 12
{{ form_end(articleForm) }}
... lines 14 - 15

Before we talk about this function, move over, refresh and... ok! It looks exactly the same. That's no accident! Calling form_widget() and passing it the entire form is just a shortcut for calling form_row() on each field individually.

This introduces an important concept in Symfony's form rendering system: the "row". The form_row() function basically adds a "wrapper" around the field - like a div - then renders the 4 components of each field: the label, the "widget" - that's the form field itself, the help text and, if needed, the validation errors.

At first, it looks like using form_row() isn't much more flexible than what we had before, except that we can reorder the fields. But, in reality, we've just unlocked quite a lot of control via a system called "form variables". Let's check those out next!

Leave a comment!

  • 2019-01-31 Diego Aguiar

    Probably another issue is that both forms have the same action URL. It may cause problems

  • 2019-01-31 Peter Kosak

    Possibly Ive solved it so this is just a future reference for someone with this issue.

    The issue is that the forms had the same name when creating them in controller.

    so you either need to create form class for each form and give it different name or when creting form in controller to use

    $form1 = $this->get('form.factory')->createNamedBuilder('form1')->add(......

  • 2019-01-31 Peter Kosak

    Hi,

    I have weird behaviour.

    I am using 2 forms on 1 twig template.

    I will create 2 forms in controller and pass them to template.


    'form1' => form1->createView(),
    'form2' => form2->createView()

    then in controller when the forms are submitted i check (there are being check in one route)
    if form 1 is valid then do this and that
    if form 2 is valid then do this and that

    everything works so far ok.

    Now if I submit only form1 with some blank fields so it will return validation errors, it will also throw errors on form2

    so in my twig if I check

    form1.vars.valid will return true -> as expected
    form2.vars.valid will return true -> never been submitted

    so I decided to check if in twig if form has been submitted and I added

    form1.vars.valid and form1.vars.sibmitted -> returns true as expected
    form2.vars.valid and form2.vars.submitted ->returns true it hasnt been submitted.

    so the question is why are both forms being submitted if I submit only 1

    to be added in twig I am properly starting and ending each form


    {{ form_start(form1) }}
    {{form_widget(form1)}}
    <button type="submit">.....</button>
    {{form_end(form1)}}

    {{ form_start(form2) }}
    {{form_widget(form2)}}
    <button type="submit">.....</button>
    {{form_end(form2)}}