If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Right now, this is a pretty simple form. We have our top level form, and then
each field below it is its own Form
object. And we now know that when you pass
this into the template, all of those Form
objects become FormView
objects.
But this will still just be 2 levels: the FormView
object on top, and the children
FormView
object for each field. But, it can get a lot more complicated than that.
To show you, go into GenusFormType
. For now, change the firstDiscoveredAt
options:
comment out widget
and attr
:
... lines 1 - 15 | |
class GenusFormType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
... lines 21 - 38 | |
->add('firstDiscoveredAt', DateType::class, [ | |
//'widget' => 'single_text', | |
//'attr' => ['class' => 'js-datepicker'], | |
'html5' => false, | |
]) | |
; | |
} | |
... lines 46 - 52 | |
} |
Refresh this immediately. Ok, the widget
option defaults to choice
, which means
that this renders as three select fields. I know, it's horribly ugly, hard to look
at... but it's a perfect example! Click into the profiler for this form to see
something really interesting. The firstDiscoveredAt
has a "+" next to it...
and three fields below it!
You see, firstDiscoveredAt
is no longer a "simple" field: it's now a field that
consists of 3 sub-fields: year
, month
, and day
. Each of these is their own
ChoiceType
field. Oh, and if you select firstDiscoveredAt
, under "View Variables",
for the first time, the compound
variable is set to true
.
We saw this compound
variable in a few places earlier. And now we know what it
means! A field is compound
if it's not really its own field, but is instead just
a container for sub-fields.
In the _form.html.twig
template, when we call form_row()
on
genusForm.firstDiscoveredAt
, Symfony tries to render the parent field, notices
that it's compound
and so, calls form_row()
on each of its three sub-fields:
{{ form_start(genusForm) }} | |
... lines 2 - 19 | |
{{ form_row(genusForm.firstDiscoveredAt) }} | |
... lines 21 - 22 | |
{{ form_end(genusForm) }} |
The result is the nice output we're already seeing.
To get more control, you could instead call form_row
on each individual field:
for year
, month
and day
:
{{ form_start(genusForm) }} | |
... lines 2 - 20 | |
{{ form_row(genusForm.firstDiscoveredAt.year) }} | |
{{ form_row(genusForm.firstDiscoveredAt.month) }} | |
{{ form_row(genusForm.firstDiscoveredAt.day) }} | |
... lines 24 - 25 | |
{{ form_end(genusForm) }} |
But notice that if this field fails validation, the error is attached to the parent
field. So you might want to keep rendering form_label(genusForm.firstDiscoveredAt)
and you definitely want to keep rendering form_errors(genusForm.firstDiscoveredAt)
,
so that the error shows up.
If you go back and refresh, you basically see the same thing as before. It's ugly, but you just learned how to take control of any level of a complex form tree.
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1", // 1.1.1
"stof/doctrine-extensions-bundle": "^1.2" // v1.2.2
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}