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!
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.0.2",
"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
}
}
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