gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
We created a form type class, used it in the controller to process the form submit and rendered it. This is pretty basic, but the form system is already doing a lot for us!
But... I think the form component can do more! Heck, I think it's been downright lazy. $data = $form->getData()
gives us an associative array with the submitted & normalized data. That's cool... but it does mean that we need to set all of that data onto the Article
object manually. Lame!
But, no more! Open ArticleFormType
. Then, go back to the Code -> Generate menu - or Cmd+N on a Mac - select "Override Methods" and choose configureOptions()
. Just like with buildForm()
, we don't need to call the parent method because it's empty. Inside add $resolver->setDefaults()
and pass an array. This is where you can set options that control how your form behaves. And, well... there aren't actually very many options. The most important, by far, is data_class
. Set it to Article::class
. This binds the form to that class.
... lines 1 - 9 | |
class ArticleFormType extends AbstractType | |
{ | |
... lines 12 - 19 | |
public function configureOptions(OptionsResolver $resolver) | |
{ | |
$resolver->setDefaults([ | |
'data_class' => Article::class | |
]); | |
} | |
} |
And... yep! That little option changes everything. Ready to see how? Back in your controller, dd($data)
.
... lines 1 - 14 | |
class ArticleAdminController extends AbstractController | |
{ | |
... lines 17 - 20 | |
public function new(EntityManagerInterface $em, Request $request) | |
{ | |
... lines 23 - 25 | |
if ($form->isSubmitted() && $form->isValid()) { | |
... line 27 | |
dd($data); | |
... lines 29 - 39 | |
} | |
... lines 41 - 44 | |
} | |
... lines 46 - 66 | |
} |
Now, move back to your browser. Watch closely: right now both fields are simple text inputs... because we haven't configured them to be anything else. But, refresh!
Whoa! The content is now a textarea
! We haven't talked about it yet, but we can, of course, configure how each field is rendered. By default, if you do nothing, everything renders as a text input. But, when you bind your form to a class, a special system - called the "form type guessing" system - tries to guess the proper "type" for each field. It notices that the $content
property on Article
is a longer text
Doctrine type. And so, it basically says:
Hey peeps! This content field looks pretty big! So, let's use a textarea field type by default.
Anyways, form field type guessing is a cool feature. But, it is actually not the super important thing that just happened.
What was? Create another breaking news story:
Orion's Belt: for Fashion or Function?
Click Create and... yes! Check it out! $form->getData()
is now an Article
object! And the title
and content
properties are already set! This is the power of the data_class
option.
When the form submits, it notices the data_class
and so creates a new Article()
object for us. Then, it uses the setter methods to populate the data. For example, the form has two fields: title
and content
. When we submit the form, it calls setTitle()
and then setContent()
. It's basically just an automatic way to do what we are already doing manually in our controller. This is awesome because we can remove code! Just say $article = $form->getData()
, done. To help PhpStorm I'll add some inline documentation that says that this is an Article
.
... lines 1 - 25 | |
if ($form->isSubmitted() && $form->isValid()) { | |
/** @var Article $article */ | |
$article = $form->getData(); | |
... lines 29 - 36 | |
} | |
... lines 38 - 65 |
That's great! Our controller is tiny and, when we submit, bonus! It even works!
In most cases, this is how I use the form system: by binding my forms to a class. But! I do want you to remember one thing: if you have a super complex form that looks different than your entity, it's perfectly okay to not use data_class
. Sometimes it's simpler to build the form exactly how you want, call $form->getData()
and use that associative array in your controller to update what you need.
Oh, and while we usually see form types bound to an entity class, that's not required! This class could be any PHP class. So, if you have a form that doesn't match up well with any of your entities, you can still use data_class
. Yep! Create a new model class that has the same properties as your form, set the data_class
to that class, submit the form, get back that model object from the form, and use it inside your controller to do whatever you want!
Oh, and if this isn't quite making sense: no worries - we'll practice this later.
Before we keep going, let's take 30 seconds to make our ugly form... beautiful! So far, we're not controlling the markup that's rendered in any way: we call a few form rendering functions and... somehow... we get a form!
Behind the scenes, all of this markup comes from a set of special Twig templates called "form themes". And yea, we can and totally will mess with these. If you're using Bootstrap CSS or Foundation CSS, ah, you're in luck! Symfony comes with a built-in form theme that makes your forms render exactly how these systems want.
Open config/packages/twig.yaml
. Add a new key called form_themes
with one element that points to a template called bootstrap_4_layout.html.twig
.
twig: | |
... lines 2 - 4 | |
form_themes: | |
- bootstrap_4_layout.html.twig |
This template lives deep inside the core of Symfony. And we'll check it out later when we talk more about form themes. Because right now... we get to celebrate! Move over and refresh. Ha! Our form is instantly pretty! The form system is now rendering with Bootstrap-friendly markup.
Next: let's talk about customizing the "type" of each field so we can make it look and act exactly how we need.
Hey Giacomo,
> i can't see the form template.
What do you mean exactly? You don't see styles for the bootstrap4 styled form? If so - then yes, you have to include "bootstrap.min.css" css file in order to apply those styles. Symfony form theme gives you only CSS classes, but actual styles should be included on the page by you.
Cheers!
Thank you!
I included boostrap CDN and that worked!
just out of curiosity, how can use boostrap after install with composer, instead of link che CDN?
Hey Giacomo,
Fairly speaking I don't know if Bootstrap can be installed with Composer :p Even if it could - you can't just link it from vendor/ dir, you have to copy it to the public/ dir somehow. Usually, there's a special bundles for it like symfony/assetic-bundle: https://github.com/symfony/... - but it was deprecated in favor of brand new WebpackEncoreBundle. You can learn more about Webpack Encore in our course here: https://symfonycasts.com/sc... - this is an amazing tool for your assets with bunch of features.
Cheers!
Hi SymfonyCasts! I have a completely custom form rendered in my twig:
{{ form_start(form) }}
<div class="mt-3">
{% for question in question %}
<input type="hidden" name="response[question][]" value="{{ question.id }}">
{% set name = 'response[answer]['~ question.id ~']' %}
{% if question.type == 'checkbox' %}
{% set name = name ~ '[]' %}
{% endif %}
{# {% if question.replies is defined %}#}
<div class="mb-3 mt-3 border">
<h3 class="mb-0" id="question-{{ question.id }}">{{ loop.index }}. {{ question.label }}</h3>
</div>
{% if question.type == 'checkbox' or question.type == 'radio' %}
<div class="answers p-1">
{% for answer in question.replies %}
{% set id = 'answer-' ~ answer.id %}
<label for="{{ id }}">{{ answer.answer }}</label>
<input type="{{ question.type }}" name="{{ name }}" id="{{ id }}">
{% endfor %}
</div>
{% elseif question.type == 'textarea' %}
<textarea name="{{ name }}" aria-labelledby="question-{{ question.id }}" cols="30" rows="5" class="form-control"></textarea>
{% elseif question.type == 'number' %}
<input type="number" name="{{ name }}" aria-labelledby="question-{{ question.id }}">
{% else %}
<input type="text" name="{{ name }}" aria-labelledby="question-{{ question.id }}" class="form-control">
{% endif %}
{# {% endif %}#}
{% endfor %}
</div>
<button class="btn btn-primary">{{ button_label|default('Submit') }}</button>
{#{{ form_end(form, {render_rest: false }) }}#}
{{ form_end(form) }}
I have no idea on how to submit the form response!
Any help?
Hey Shane,
Ha, good question! :) The button should have type="submit" to be able to submit the form, try to add this special type to your button and browser will know that it should send the form on that button hit, i.e.:
<button type="submit" class="btn btn-primary">{{ button_label|default('Submit') }}</button>
Cheers!
Hi,
is there a way to add an "a"-tag, or any other, to the label in the FormType??
[ 'label' => '<a href="#">....']```
just prints exactly what I typed in instead of transforming.
Thx
Oliver
Hey Oliver W.
By default Twig escapes your strings to avoid any HTML injection, if you really want to print some HTML you need to use the raw
filter.
Just do this: [ 'label' => '<a href="#">....']|raw
Docs reference: https://twig.symfony.com/doc/2.x/filters/raw.html
Cheers!
Hi MolloKhan
thx for your assistance but I need to do this in my form declaration not in the template!?
For now I made it in the template with a manual <label> combined with {{ form_widget() }} and {{ form_errors() }}. That's more work but I know what to do.
That may work but there are other 2 options depending on your needs. If you want to write the HTML inside the FormType class, then you will have to override the label value inside the template so you can use the raw
filter. Something like this:
// form_template.html.twig
{{ form_label(form.some_field, null, {
label: form.some_field.vars.label | raw
}) }}
or, just write the HTML inside the form template and you wouldn't have to use the raw filter
Cheers!
Hi, after adding in ArticleFormType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content')
;
}
in ArticleAdminController->new in
$form->handleRequest($request);
when i try to submit form i'm getting error:<br />The property "App\Entity\Article::$title" is not readable because it is typed "string". You should initialize it or declare a default value instead.<br />
ok, I have managed to fixed it. I added previously types to fields in Article Entity and they just needed to be initialized.
Hey Jacek
That's great that you fixed it. How do you think is it true that sometimes you can fix anything after asking about it? :) For example I'm always find a way to fix issue, after asking colleague :)))
Cheers!
Yep, it's the rubber duck debugging method :) https://en.wikipedia.org/wi...
Thanks for great tutorial :)
Hello, I have a drop down list of EntityType, i want to display a list grouped by family (so using form group) :
` ->add('idEtatMatriceDetails', EntityType::class,[
'class' => EtatMatriceDetails::class,
'choice_label' => 'LibelleEtatMatriceDetails',
'group_by' => function($choice,$key,$val){ // $choice is an instance of EtatMatriceDetails
switch($choice->getIdEtatMatrice()->getIdEtatMatrice())
{
case 1:
return 'FAMILY_ONE';
break;
case 2:
return 'FAMILY_TWO';
break;
....`
under FAMILY_ONE, i can have several options, however, some of these options are disabled (there is a column for that flag), so I want to filter these options, how can I do this in the closure? can I craft a special getter in the entity that filter those values? Thank you
Hey Yvon,
I see you mentioned that $choice in that closure is an instance of EtatMatriceDetails entity, so yes, I think you can create a new method in that entity with the logic and use it inside the closure to group them, or you can do the same logic directly in that closure. Have you tried it? Did you have any problems with this implementation?
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.2.1
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.1.6
"symfony/console": "^4.0", // v4.1.6
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "^4.0", // v4.1.6
"symfony/framework-bundle": "^4.0", // v4.1.6
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/security-bundle": "^4.0", // v4.1.6
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/twig-bundle": "^4.0", // v4.1.6
"symfony/validator": "^4.0", // v4.1.6
"symfony/web-server-bundle": "^4.0", // v4.1.6
"symfony/yaml": "^4.0", // v4.1.6
"twig/extensions": "^1.5" // v1.5.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
"symfony/dotenv": "^4.0", // v4.1.6
"symfony/maker-bundle": "^1.0", // v1.8.0
"symfony/monolog-bundle": "^3.0", // v3.3.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.1.6
}
}
Hallo, i can't see the form template.
twig.yaml:
<!DOCTYPE html>
<html lang="it">
</html>
{% extends 'base.html.twig' %}
{% block body %}
{% endblock %}
<!DOCTYPE html>
<html lang="it">
</html>
`
I'm using symfony 5 with my own project (not spacebar).
Any suggestions?
Thanks,
Giacomo.