If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOne 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
!
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!
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!
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 optionsallow_add
,allow_delete
,delete_empty
,entry_options
andentry_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?
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 myconfigure()
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.
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.
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!
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!
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;
}
});
Oh, sorry, I forgot to point you to the docs but it's great that you find it by yourself
Cheers!
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.
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!
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
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).
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!
// 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
}
}
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.