Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Setup: For Dependent Select Fields

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

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

Login Subscribe

We're going to tackle one of the most annoying things in Symfony's form system, and, I hope, make it as painless as possible... because the end result is pretty cool!

Log in as admin2@thespacebar.com, password engage and then go to /admin/article. Click to create a new article. Here's the goal: on this form, I want to add two new drop-down select elements: a location drop-down - so you can choose where in the galaxy you are - and a second dropdown with more specific location options depending on what you chose for the location. For example, if you select "Near a star" for your location, the next drop-down would update to be a list of stars. Or, if you select "The Solar System", the next drop-down will be a list of planets.

Adding the First Select Field

This is called a "dependent form field", and, unfortunately, it's one of the trickier things to do with the form system - which is exactly why we're talking about it! Let's add the first new field. Find your terminal and run

php bin/console make:entity

Modify the Article entity and create a new field called location. Make it a string field with "yes" to nullable in the database: the location will be optional. Now run:

php bin/console make:migration

and open the Migrations/ directory to check out that new file.

... lines 1 - 12
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE article ADD location VARCHAR(255) DEFAULT NULL');
}
... lines 20 - 29

No surprises here, so let's go back and run it:

php bin/console doctrine:migrations:migrate

Perfect!

Next, open the ArticleFormType so we can add the new field. Add location and set it to a ChoiceType to make it a drop-down. Pass the choices option set to just three choices. The Solar System set to solar_system, Near a star set to star and Interstellar Space set to interstellar_space.

... lines 1 - 15
class ArticleFormType extends AbstractType
{
... lines 18 - 24
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 27 - 29
$builder
... lines 31 - 39
->add('location', ChoiceType::class, [
'choices' => [
'The Solar System' => 'solar_system',
'Near a star' => 'star',
'Interstellar Space' => 'interstellar_space'
],
... line 46
])
;
... lines 49 - 54
}
... lines 56 - 65

The choices on the ChoiceType can look confusing at first: the key for each item will be what's actually displayed in the drop down. And the value will be what's set onto our entity if this option is selected. So, this is the string that will ultimately be saved to the database.

Let's also add one more option: required set to false.

... lines 1 - 29
$builder
... lines 31 - 39
->add('location', ChoiceType::class, [
... lines 41 - 45
'required' => false,
])
;
... lines 49 - 65

Remember: as soon as we pass the field type as the second argument, the form field type guessing stops and does nothing. Lazy! It would normally guess that the required option should be false - because this field is not required in the database, but that won't happen. So, we set it explicitly.

Cool! Let's try it - go refresh the form. Ha! It works... but in a surprising way: the location field shows up... all the way at the bottom of the form.

The reason? We forgot to render it! Open templates/article_admin/_form.html.twig. When you forget to render a field, {{ form_end() }} renders it for you. It's kind of a nice reminder that I forgot it. Of course, we don't really want to render it all the way at the bottom like this. Instead, add {{ form_row(articleForm.location) }}

{{ form_start(articleForm) }}
... lines 2 - 5
{{ form_row(articleForm.location) }}
... lines 7 - 12
{{ form_end(articleForm) }}

Oh, and I forgot: we'll want an "empty" choice at the top of the select. In the form, add one more option: placeholder set to Choose a location.

... lines 1 - 24
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 27 - 39
->add('location', ChoiceType::class, [
'placeholder' => 'Choose a location',
... lines 42 - 47
])
;
... lines 50 - 55
}
... lines 57 - 66

Refresh! So much nicer! And if we submitted the form, it would save.

Adding the Second Field

So, let's add the second field! Go back to your terminal and run:

php bin/console make:entity

Update the Article entity again and create a new field called specificLocationName, which will store a string like "Earth" or "Mars". Make this "yes" to nullable in the database - another optional field.

When you're done, make the migration:

php bin/console make:migration

And... I'm pretty confident that migration won't have any surprises, so let's just run it:

php bin/console doctrine:migrations:migrate

Sweet! Back in ArticleFormType, copy the location field, paste, and call it specificLocationName. For the placeholder, use Where exactly?. And for the choices... hmm - this is where things get interesting. I'll just add a dummy "TODO" option to start.

... lines 1 - 24
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 27 - 48
->add('specificLocationName', ChoiceType::class, [
'placeholder' => 'Where exactly?',
'choices' => [
'TODO' => 'TODO'
],
'required' => false,
])
;
... lines 57 - 62
}
... lines 64 - 73

Back in the form template, copy the location render line, paste it right below, and change it to specificLocationName.

{{ form_start(articleForm) }}
... lines 2 - 6
{{ form_row(articleForm.specificLocationName) }}
... lines 8 - 13
{{ form_end(articleForm) }}

When we refresh now... no surprise: it works. Here's our location and here's our specificLocationName. But... this is not how we want this to work. When "solar system" is selected, I want this second drop-down to contain a list of planets. If "Near a star" is selected, this should be a list of stars. And if "Interstellar space" is selected, I don't want this field to be in the form at all. Woh.

The way to solve this is a combination of form events, JavaScript and luck! Ok, I hope we won't need too much of that. Let's start jumping into these topics next!

Leave a comment!

6
Login or Register to join the conversation
Remus M. Avatar
Remus M. Avatar Remus M. | posted 4 years ago

you have no idea how much i have waited for this part :D

1 Reply

Haha, well, it was thanks to users like you who were asking for this that we finally covered it. This is a *tough* topic I admit!

2 Reply
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago | edited

My error is not particularly about this chapter, but I am getting this:
App\Entity\Meetstaat object not found by the @ParamConverter annotation.
I also googled and read some pages on the Symfony docs but I can't seem to understand the issue.

`
namespace App\Entity;

use App\Repository\MeetstaatRepository;
use Doctrine\ORM\Mapping as ORM;

/**

  • @ORM\Entity(repositoryClass=MeetstaatRepository::class)
    */
    class Meetstaat
    {
    ...
    `
Reply

Hey Farry7,

It looks to me that the Meetstaat record you're trying to access doesn't exist on your database. Double-check that the ID you're passing to the route exists on your system. Cheers!

Reply
Jose M. Avatar
Jose M. Avatar Jose M. | posted 4 years ago

Hi there!

Any idea how I can add a "selected" option on a ChoiceType element???

Long time web developer, but I'm new to sf... and I'm implementing a basic CRUD and one of the fields needs to be a select. The value of the select field is stored in the db and when I read the value back... I need to pre-select that particular value on the select... I've been googling this for 2 days now and I would think this is something that should be trivial.

Any ideas?

Reply

Hey Jose M.

Have you tried out the "data" option?
https://symfony.com/doc/cur...

If you bind an object to your form, then its initial value will be taken from the object

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice