Forms
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
:
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:
// ... 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!
// ... 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:
// ... 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.
// ... 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
:
{{ 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:
{{ 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
:
// ... 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:
// ... 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!
I found the issue.
WAS OK BEFORE(sf 2.8):
$builder->add('title', 'text')
->add('samsCharacterName', 'text')
->add('isMainCharacter', 'checkbox', array(
'required' => false
))
->add('rating', 'integer')
->add('releasedAt', 'date', array(
'widget' => 'single_text',
));
SHOULD BE (from sf 2.8):
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
$builder->add('title', TextType::class)
->add('samsCharacterName', TextType::class)
->add('isMainCharacter', CheckboxType::class, array(
'required' => false
))
->add('rating', IntegerType::class)
->add('releasedAt', DateType::class , array(
'widget' => 'single_text',
));