06.

Forms

Share this awesome video!

|

Tip

This screencast shows the old, 2.7 and earlier form syntax. But, the code blocks below have been updated to show the new syntax!

Now that we've got our entity let's create a form! Click on AppBundle, press our handy shortcut command+n, and if you search form you'll find that option in the menu! It's all coming together!

We'll call this MovieType:

<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MovieType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
}
public function configureOptions(OptionsResolver $resolver)
{
}
}

Notice it was actually smart enough to put that inside of a Form directory and build out the whole structure that we'll need. All I need next is for it to pour me a cup of coffee.

The first thing we always do inside of here is call $resolver->setDefaults(array()) and pass the data_class option so that it binds it to our Movie entity. Conveniently, this gives us more autocomplete: we can just type Movie and it adds the rest:

23 lines | src/AppBundle/Form/MovieType.php
// ... lines 1 - 8
class MovieType extends AbstractType
{
// ... lines 11 - 15
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Movie'
));
}
}

Configuring Form Fields

This will even help us build our fields. If we type $builder->add('') here, because this is bound to our Movie entity, it knows all the properties we have there. So let's plug in our property of title which should be a text field, samsCharacterName which is probably a text field as well and isMainCharacter which will be a checkbox. We'll want to make sure that we prevent html5 validation on that. A third argument of 'required' => false will take care of that for us and even that has autocomplete. It's madness!

35 lines | src/AppBundle/Form/MovieType.php
// ... lines 1 - 5
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
// ... lines 7 - 8
use Symfony\Component\Form\Extension\Core\Type\TextType;
// ... lines 10 - 12
class MovieType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class)
->add('samsCharacterName', TextType::class)
->add('isMainCharacter', CheckboxType::class, array(
'required' => false,
))
// ... lines 22 - 25
}
// ... lines 27 - 33
}

Let's also include rating as an integer field and lastly, releasedAt which is a date field:

35 lines | src/AppBundle/Form/MovieType.php
// ... lines 1 - 6
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
// ... lines 10 - 12
class MovieType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class)
// ... lines 18 - 21
->add('rating', IntegerType::class)
->add('releasedAt', DateType::class, array(
// ... line 24
));
}
// ... lines 27 - 33
}

Digging into Options and Fields

We can control how this date field renders: by default with Symfony it would be 3 select boxes. I'll set a widget option, except I don't remember what value to set that to. No worries, I'll just hold the command key over the widget option and it will take me straight to where that is setup inside of the core code:

// ... lines 1 - 11
namespace Symfony\Component\Form\Extension\Core\Type;
// ... lines 13 - 26
class DateType extends AbstractType
{
const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM;
// ... lines 30 - 167
public function configureOptions(OptionsResolver $resolver)
{
// ... lines 170 - 202
$resolver->setDefaults(array(
// ... lines 204 - 206
'widget' => 'choice',
// ... lines 208 - 224
));
// ... lines 226 - 235
$resolver->setAllowedValues('widget', array(
'single_text',
'text',
'choice',
));
// ... lines 241 - 245
}
// ... lines 247 - 321
}

Why is that awesome you ask? Because I can search for what I need inside of here and boom setAllowedValues single_text, text and choice. So let's paste single_text back into our file.

35 lines | src/AppBundle/Form/MovieType.php
// ... lines 1 - 6
use Symfony\Component\Form\Extension\Core\Type\DateType;
// ... line 8
use Symfony\Component\Form\Extension\Core\Type\TextType;
// ... lines 10 - 12
class MovieType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class)
// ... lines 18 - 22
->add('releasedAt', DateType::class, array(
'widget' => 'single_text'
));
}
// ... lines 27 - 33
}

That trick of holding command and clicking works for the field types too: command+click integer and suddenly you're inside the class that provides this!

// ... lines 1 - 11
namespace Symfony\Component\Form\Extension\Core\Type;
// ... lines 13 - 19
class IntegerType extends AbstractType
{
// ... lines 22 - 37
public function configureOptions(OptionsResolver $resolver)
{
// ... lines 40 - 47
$resolver->setDefaults(array(
// deprecated as of Symfony 2.7, to be removed in Symfony 3.0.
'precision' => null,
// default scale is locale specific (usually around 3)
'scale' => $scale,
'grouping' => false,
// Integer cast rounds towards 0, so do the same when displaying fractions
'rounding_mode' => IntegerToLocalizedStringTransformer::ROUND_DOWN,
'compound' => false,
));
// ... lines 58 - 69
}
// ... lines 71 - 78
}

It's like being teleported but, you know, without any risk to your atoms. You can even use this to take you to the property inside of Movie for that specific field.

Form Rendering

Our form is setup so let's go ahead and create this inside of our controller, $form = $this->createForm(new MovieType(), $movie);. Like always, we need to pass our form back into our template with $form->createView():

// ... lines 1 - 5
use AppBundle\Form\MovieType;
// ... lines 7 - 10
class MovieController extends Controller
{
// ... lines 13 - 15
public function newAction()
{
$movie = new Movie();
$form = $this->createForm(MovieType::class, $movie);
return $this->render('movie/new.html.twig', array(
'quote' => 'If my answers frighten you then you should cease asking scary questions. (Pulp Fiction)',
'form' => $form->createView()
));
}
// ... lines 27 - 34
}

Time to render this! Click into the new.html.twig template, ah that's right my form is actually going to be over here in _form.html.twig. It shouldn't surprise you that you'll get autocomplete here on things like form_start and form_end:

10 lines | app/Resources/views/movie/_form.html.twig
{{ form_start(form) }}
// ... lines 2 - 8
{{ form_end(form) }}

Want more autocomplete awesomeness you say? You're mad! But ok: type {{ form_row(form) }} and it'll auto-complete the title field for you. So we'll plug in all of our fields here:

10 lines | app/Resources/views/movie/_form.html.twig
{{ form_start(form) }}
{{ form_row(form.title) }}
{{ form_row(form.samsCharacterName) }}
{{ form_row(form.isMainCharacter) }}
// ... lines 5 - 8
{{ form_end(form) }}

And if I forget one of them, I can hit control+space to bring up all of my options. This will also show you some other methods that exist on that FormView object. Ah ok so we still have rating and releasedAt to add here. Clean up a bit of indentation here and perfect!

{{ form_start(form) }}
{{ form_row(form.title) }}
{{ form_row(form.samsCharacterName) }}
{{ form_row(form.isMainCharacter) }}
{{ form_row(form.rating) }}
{{ form_row(form.releasedAt) }}
<button type="submit" class="btn btn-primary">Save</button>
{{ form_end(form) }}

Time to try this out: back to our new movies page, refresh and there we go! It renders with no problems other than the fact that this is a form only it's developer could love. And well, maybe not even that: I'm going to make it prettier. In config.yml, down in the twig key add form_themes:

83 lines | app/config/config.yml
// ... lines 1 - 34
twig:
// ... lines 36 - 37
form_themes:
// ... lines 39 - 83

Now this should autocomplete, but for whatever reason this one key is not doing that. But for the most part, you will see autocompletion inside of your configuration files.

Form Theming

Right here let's plug in the bootstrap form theme: and the plugin isn't perfect because we don't get autocomplete on this either. But I do know that there is a file inside the project for bootstrap, so I'll go to find, file and start typing in bootstrap. We want the one called bootstrap_3_layout.html.twig. To cheat, I'll just copy that file name and paste that in here:

83 lines | app/config/config.yml
// ... lines 1 - 34
twig:
// ... lines 36 - 37
form_themes:
- bootstrap_3_layout.html.twig
// ... lines 40 - 83

Refresh with our new form theme and .... Awesome!