Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Deep Field Configuration

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

One other property that we have inside of User is $roles, which actually stores an array of the roles this user should have:

... lines 1 - 15
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
... lines 18 - 27
#[ORM\Column(type: Types::JSON)]
private array $roles = [];
... lines 30 - 281
}

That's probably a good thing to include on our admin page. And fortunately, EasyAdmin has an ArrayField!

ArrayField

Check it out! Say yield ArrayField::new('roles'):

... lines 1 - 6
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
... lines 8 - 14
class UserCrudController extends AbstractCrudController
{
... lines 17 - 21
public function configureFields(string $pageName): iterable
{
... lines 24 - 36
yield ArrayField::new('roles');
}
}

And then head back to your browser. Over on the index page... nice! It renders as a comma-separated list. And on the "Edit" page... oh, that's really cool! It added a nice widget for adding and removing roles!

Adding Help Text to Fields

The only tricky part might be remembering which roles are available. Right now, you have to type each in manually. We can at least help our admins by going back to our array field and implementing a method called ->setHelp(). Add a message that includes the available roles:

... lines 1 - 6
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
... lines 8 - 14
class UserCrudController extends AbstractCrudController
{
... lines 17 - 21
public function configureFields(string $pageName): iterable
{
... lines 24 - 36
yield ArrayField::new('roles')
->setHelp('Available roles: ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_MODERATOR, ROLE_USER');
}
}

Now when we refresh... much better!

->setFormType() and ->setFormTypeOptions()

But, hmm. Now that I see this, it might look even better if we had check boxes. So let's see if we can change the ArrayField to display check boxes. Hold Cmd and open this core class.

This is really interesting, because you can actually see how the field is configured inside of its new() method. It sets the template name (we'll talk about templates later), but it also sets the form type. Behind the scenes, the ArrayField uses a CollectionType. If you're familiar with the Symfony Form Component, you know that, to render check boxes, you need the ChoiceType. I wonder if we can use ArrayField... but override its form type to be ChoiceType.

Let's... give it a try!

First, above this, add $roles = [] and list our roles. Then, down here, after ->setHelp(), one of the methods we can call is ->setFormType()... there's also ->setFormTypeOptions(). Select ->setFormType() and set it to ChoiceType::class:

... lines 1 - 13
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class UserCrudController extends AbstractCrudController
{
... lines 18 - 22
public function configureFields(string $pageName): iterable
{
... lines 25 - 38
$roles = ['ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_MODERATOR', 'ROLE_USER'];
yield ArrayField::new('roles')
->setFormType(ChoiceType::class)
... lines 42 - 46
}
}

Then ->setFormTypeOptions()... because one of the options that you must pass to this form type is choices. Set this to array_combine() and pass $roles twice:

... lines 1 - 15
class UserCrudController extends AbstractCrudController
{
... lines 18 - 22
public function configureFields(string $pageName): iterable
{
... lines 25 - 38
$roles = ['ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_MODERATOR', 'ROLE_USER'];
yield ArrayField::new('roles')
->setFormType(ChoiceType::class)
->setFormTypeOptions([
'choices' => array_combine($roles, $roles),
... lines 44 - 45
]);
}
}

I love rolls!

I know, that looks weird. This will create an array where these are both the keys and the values. The result is that these will be both the values that are saved to the database if that field is checked and what is displayed to the user. Lastly, set multiple to true - because we can select multiple roles - and expanded to true... which is what makes the ChoiceType render as check boxes:

... lines 1 - 15
class UserCrudController extends AbstractCrudController
{
... lines 18 - 22
public function configureFields(string $pageName): iterable
{
... lines 25 - 38
$roles = ['ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_MODERATOR', 'ROLE_USER'];
yield ArrayField::new('roles')
->setFormType(ChoiceType::class)
->setFormTypeOptions([
'choices' => array_combine($roles, $roles),
'multiple' => true,
'expanded' => true,
]);
}
}

Alrighty! Let's see what happens. Refresh and... it... explodes! Exciting!

An error occurred resolving the options of ChoiceType: The options allow_add, allow_delete, delete_empty, entry_options and entry_type do not exist.

Hmm... I recognize these options as options that belong to the CollectionType, which is the type that the ArrayField was originally using. This tells me that something, somewhere is trying to add these options to our form type... which we don't want because... we're not using CollectionType anymore!

So... who is setting those options? This is tricky. You might expect to see them set inside of ArrayField. But... it's not here! What mysterious being is messing with our field?

Hello Field Configurators

The answer is something called a Configurator.

Scroll back down to vendor/. I've already opened easycorp/easyadmin-bundle/src/. Earlier, we were looking at the Field/ directory: these are all the built-in fields.

After a field is created, EasyAdmin runs each through a Configurator system that can make additional changes to it. This Configurator/ directory holds those. There are a couple of them - like CommonPreConfigurator - that are applied to every field. It returns true from supports()... and does various normalizations on the field. CommonPostConfigurator is another that applies to every field.

But then, there are also a bunch of configurators that are specific to just one... or maybe a few... field types, including ArrayConfigurator. This configurator does its work when the $field is an ArrayField. The $field->getFieldFqcn() is basically helping to ask:

Hey, is the current field that's being configured an ArrayField? If it is, then call my configure() method so I can do some stuff!

And... yup! Here is where those options are being added. The Configurator system is something we're going to look at more later. Heck we're even going to create our own! For now, just be aware it exists.

Refactoring to ChoiceField

So, hmm. In our situation, we don't want the ArrayConfigurator to do its work. But, unfortunately, we don't really have a choice! The Configurator is always going to apply its logic if we're dealing with an ArrayField.

And actually, that's fine! Back in UserCrudController.php, I didn't realize it at first, but there's also a ChoiceField!

... lines 1 - 7
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
... lines 9 - 14
class UserCrudController extends AbstractCrudController
{
... lines 17 - 21
public function configureFields(string $pageName): iterable
{
... lines 24 - 38
yield ChoiceField::new('roles')
... lines 40 - 42
}
}

Hold Cmd or Ctrl to open it. Yup, we can see that it already uses ChoiceType. So, we don't need to take ArrayField and try to turn it into a choice... there's already a built-in ChoiceField made for this!

And now we don't need to set the form type... and we don't need the help or the form type options. I probably could set the choices that way, but the ChoiceField has a special method called ->setChoices(). Pass that same thing: array_combine($roles, $roles). For the other options, we can say ->allowMultipleChoices() and ->renderExpanded():

... lines 1 - 14
class UserCrudController extends AbstractCrudController
{
... lines 17 - 21
public function configureFields(string $pageName): iterable
{
... lines 24 - 37
$roles = ['ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_MODERATOR', 'ROLE_USER'];
yield ChoiceField::new('roles')
->setChoices(array_combine($roles, $roles))
->allowMultipleChoices()
->renderExpanded();
}
}

How nice is that?

Let's try this thing. Refresh and... that is what I was hoping for! Back on the index... ChoiceType still renders as a nice comma-separated list.

Oh, and by the way: if you want to see the logic that makes ChoiceType render as a comma-separated list, there a ChoiceConfigurator.php. If you open that... and scroll to the bottom - beyond a lot of normalization code - here it is: $field->setFormattedValue() where it implodes the $selectedChoices with a comma.

Rendering ChoiceList as Badges

Oh, and speaking of this type - let me close some core classes - one other method we can call is ->renderAsBadges():

... lines 1 - 14
class UserCrudController extends AbstractCrudController
{
... lines 17 - 21
public function configureFields(string $pageName): iterable
{
... lines 24 - 38
yield ChoiceField::new('roles')
... lines 40 - 42
->renderAsBadges();
}
}

That affects the "formatted value" that we just saw... and turns it into these little guys. Cute!

Next, let's handle our user's $avatar field, which needs to be an upload field!

Leave a comment!

9
Login or Register to join the conversation
Delenclos-A Avatar
Delenclos-A Avatar Delenclos-A | posted 4 months ago

Hi, I need help please,

In the index page on each row , I just want to add a condition in a field: For example If you're my friend I see your birthday, else "birthday is not available".

In reality, it's an asociationfield and I'm trying to use a voter to testing if the value field, can be displayed.or not.

I can imagine it's easy... but not for me.

Reply

Hey @Delenclos-A

I think you can use the formatValue() method of the association field. It accepts a callback function where you can add your rendering logic

Cheers!

Reply
Delenclos-A Avatar
Delenclos-A Avatar Delenclos-A | MolloKhan | posted 4 months ago | edited

Thank you so much, It's work fine. For me it was difficult to get the current entity. In chapter 13 I found how.

            ->formatValue(function ($value, Dance $dance) {
                $show = $dance->getPotcommun()->isShowAll();
                if ($this->getUser() !== $dance->getCreatedBy()) {
                    if ($show) {
                        return $value;
                    } else {
                        return 'not Available';
                    }
                } else {
                    return $value;
                }
            });
Reply

Oh, sorry, I forgot to point you to the docs but it's great that you find it by yourself

Cheers!

Reply
Rudi-T Avatar

A full example where you need a custom Configurator and Form Type could be useful.
For example how to make a phone number field that exists of 2 components:
1 the country code selector (kinda like Country field)
2 the phone number.

I feel like the docs and these tutorials don't go deep enough for these custom fields.

Reply

Hey Rudi,

Yes, we do not cover something like this in this tutorial, but mostly because it is almost not related to EA :) You just need to add a select field to your form that will render the list of countries with their phone codes. And another text input field where you will write the actually phone number, that's it. You will just need to make it prettier, probably place the fields a bit better of you want it inline. But that can be achieve with CSS and templates overriding - we're talking about it in this course. So mostly this is a specific routing task.

Cheers!

Reply
Peter A. Avatar
Peter A. Avatar Peter A. | posted 1 year ago

I have ORM Column which is type JSON. But I'd like to use some widget to do JSON Editing.. maybe (ckEditor with json). Any guide or tips her.
Thanks

Reply
Cameron Avatar
Cameron Avatar Cameron | posted 1 year ago

Could we see a comparison (or even a tutorial on) ApiPlatform's react admin?

https://api-platform.com/do...

I imagine it would be performant and may have auto config using it's annotations.

A comparison with LowCode apps that are used to build SQL admin interfaces would be another useful approoach as these are more generalized toolsets and not specific to PHP or symfony (can be used on any type of project and may allow for things like webhooks for additional customization on save / edit / delete).

Reply

Hey Fox C.!

> Could we see a comparison (or even a tutorial on) ApiPlatform's react admin?

I've wanted to see about getting a tutorial about this for awhile, but I haven't had time yet. It's still on the list, but will be awhile. Without knowing enough about ApiPlatform's react admin interface, my uneducated guess would be that:

A) If you're building a "traditional" app that returns HTML pages, use EasyAdmin
B) If you're building an API, then use ApiPlatform's react admin.

So the correct admin choice sort of "follows" the type of app you've built in the first place :).

> A comparison with LowCode apps that are used to build SQL admin interfaces would be another useful approoach as these are more generalized toolsets and not specific to PHP or symfony (can be used on any type of project and may allow for things like webhooks for additional customization on save / edit / delete).

Did you have a specific LowCode tool in mind? Are you thinking about one that connects directly to your database? I don't have any experience, but in general, this seems like a worthwhile "direction" in the future.

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0
    }
}
userVoice