EasyAdmin! For an Awesomely Powerful Admin Area
Master EasyAdmin's power features, like auto-completion widgets, boolean fields toggling, and bespoke customisation.
- 4475 students
- EN Captions
- EN Script
- Certificate of Completion
Your Guides
About this course
So... your site needs an admin area. Do yourself a favor and skip all that custom code and jump straight into EasyAdmin bundle. Why #1? Because it'll take you a fraction of the time to build what you need. Why #2? Because it'll be even better than what you would build by hand, including built-in widgets for auto-completion and toggling boolean fields. It's... pretty sweet.
In this tutorial, we'll learn how to admin interfaces that are highly customized:
- Install & Bootstrapping the bundle
- Dashboards! CRUD controllers!
- All about Fields
- Customize everything: what properties to display, how they render, help messages, sorting, filters... and more!
- Override templates... at many different levels
- Take control of your forms
- Handling security
- Adding custom actions (and removing others)
- Updating and configuring the menu (like adding a link to kitten videos!)
- Hooking into events to do things before or after an entity is saved
- Adding custom CSS/JS behaviors to the page with Webpack Encore
- ... and more
So let's do a little bit of work for a lotta bit results (note: "lotta bit" is a term I just made up).
Tip
Love EasyAdmin? Consider sponsoring its maintainer Javier Eguiluz!
Next courses in the Symfony 6: Tools, Tools, Tools! section of the Symfony 6 Track!
62 Comments
Hey Nina!
Thank you for your interest in SymfonyCasts tutorials! Unfortunately, we cannot add new chapters to the tutorials we've already released, but we can consider this as a good topic to cover for our next tutorial about EasyAdmin. We don't have specific plans to make it in a nearest feature, but I'll add it to our ideas pool.
If you want to know more about useEntryCrudForm()
and renderAsEmbeddedForm()
- take a look at the official docs:
- https://symfony.com/bundles/EasyAdminBundle/current/fields/CollectionField.html#useentrycrudform
- https://symfony.com/bundles/EasyAdminBundle/current/fields/AssociationField.html#renderasembeddedform
Unfortunately, I personally haven't used those features yet. You can try to handle this task with that feature, but if it does not work well - you can always create a custom controller/action and write any custom logic you want there. I hope that helps!
Cheers!
Hi everyone,
Easyadmin is great, no doubt about it.
But...
I can't believe that there is no way to create dependent select fields, like describe here for symfony: https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms
I mean this is a really natural behaviour, an admin should provide this.
Anyway, now that my project is really advanced, I realise I'm in need of this so...
So what will be the best way to try to "hack' this and make it work ?
Hey Christina-V!
I can't believe that there is no way to create dependent select fields, like describe here for symfony: https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms
I agree. This is probably THE thing in Symfony in general that is much harder than it should be. In general, my way of solving this these days is by using LiveComponents. However, I haven't used live components in EasyAdmin yet - it would require some research to figure out how to reuse the form built by EasyAdmin from inside of your component. A compelling thing to try, but I just haven't had a chance to look into it yet.
So, for this, the process would look something like this comment - https://github.com/EasyCorp/EasyAdminBundle/issues/4716#issuecomment-1127979067 - but more specifically, I would:
A) In configureCrud()
, I would call:
$crud->setFormOptions(['attr' => [
'data-controller' => 'admin-dependent-fields',
'data-admin-dependent-fields-url-value' => $this->container->get(AdminUrlGenerator::class)->setAction(Action::NEW)->generateUrl()
]])
This should initialize a Stimulus controller called admin-dependent-fields
and pass a "value" called url
to it.
B) In configureFields()
, we're going to add an "action" to the first field:
public function configureFields(string $pageName): iterable
{
yield ChoiceField::new('state')->setFormTypeOption('attr', ['data-action' => 'admin-dependent-fields#changeState']);
}
C) Create the corresponding Stimulus controller:
// assets/controllers/admin-dependent-fields-controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static values = {
url: String
}
changeState(event) {
const countryElement = event.currentTarget;
const data = {};
data[countryElement.name] = countryElement.value;
fetch(this.urlValue, { method: 'POST' })
.then(async (response) => {
const currentStateElement = document.getElementById('the-id-attribute-on-state-field');
const newElement = document.createElement('div');
newElement.innerHTML = await response.text();
const newStateElement = newElement.querySelector('#the-id-attribute-on-state-field');
// I'm using parentElement because I'm assuming we might want to replace the entire
// parent around the select, not just the select
currentStateElement.parentElement.replaceWith(newStateElement.parentElement);
});
}
}
D) Then you will STILL need the form modification stuff found here - https://github.com/EasyCorp/EasyAdminBundle/issues/4716#issuecomment-1127979067
Phew! Probably I made some mistakes, but hopefully this will help. But yes, I hate this solution - that is WAY WAY too much work to accomplish this - I agree completely
Cheers!
Thanks for the reply Ryan!
It's duable I think. I'll have a look probably this week, and let you know the results ;-0
Hi Knp lecturers,
thank you for this great tutorial.
I'm starting to learn symfony 6.
Looking forward for next symfony 6 track tutorial
Hey Suabahasa!
Thank you for your interest in SymfonyCasts tutorials and your feedback! Good time to start learning it ;) We're going to release a few more Symfony 6 tutorials shortly, and then we will create a Sf6 track - we just need at least a few courses there :) Thank you for your patience!
Cheers!
Stay away from this course. It is full of errors, nothing really works. It is also not up to date. It urgently needs to be revised. For the first two "lessons" I had to spend several hours looking for a solution. Because the database in Docker didn't work or it used a private (outdated) package.
Hey Stephen!
Really sorry about the trouble! This is definitely not the experience we want people to have.
And you're right, this tutorial (which is one of our most popular) is showing its age. We need to re-record it and I bet we will at some point over the next 6 months.
Keeping tutorials working over time is a tough job. We aim to do it by adding video notes highlighting differences and changes. This is definitely a manual and imprecise job. And it sounds like we're missing note or Docker fix! We're going to check into that and see if we can reproduce the issue. Thanks for bringing it to our attention :).
Thanks!
I have the same problem. Cannot even go past the first two lessons. Can author please check them and comment on the fixes / changes needed to get it going?
Hey Gorkyman,
We're sorry to hear you have issues with following this tutorial, but please, don't worry, our support team will help you if you get stuck :)
First of all, this course was recently revisited and it should work out of the box for you. However, if you have any specific issues - please, feel free to share more information regarding the issue and we will help you. What exactly are you doing and what exactly does not work for you?
Cheers!
When I trie to add a user at the dashboard I get the following error:
You don't have enough permissions to access the instance of the "App\Entity\User" entity with id = .
I'm Logged in as superadmin@example.com with role: ROLE_SUPER_ADMIN
What's wrong?
Hey Tim,
Hm, as you can see from the error message, there's no user. It migth be you were logged out? please, double check you're still logged in. Also, you can find the place where that error is thrown and see what exactly you're passing there as the ID and why it's empty, probably you're doing some checks on an incorrect object? Try to debug things with dump()
or dd()
.
Cheers!
Hi Victor,
Thanks for the relpy. I just checked and I'm still Logged In. I thought this should work when I completed the tutorial. I will try the dump en dd.
Could it be that there is no "new" action defined at the UserCrudController.php?
Hey Tim,
Hm, it might be, you can temporarily allow new action and see if it fixes the problem. But mostly it depends on your configuration and what exactly you're trying to do in EA, I think some configuration isn't perfect in your case. You can spy at the final code in the finish/ folder of downloaded course code to see if it works there or no.
Cheers!
I did find a solution now:
Allowing the Access of an empty EntityDto is needed.
Here is my working code for the AdminUserVoter
. (Only SuperAdmins can add new user)
protected function supports(string $attribute, mixed $subject): bool
{
return match ($attribute) {
'ADMIN_USER_EDIT' => $subject instanceof User,
'EA_ACCESS_ENTITY' => $subject instanceof EntityDto,
default => false
};
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface && self::EDIT === $attribute) {
return false;
}
if (!$subject instanceof User && self::EDIT === $attribute) {
throw new \LogicException('Subject ist not an instance of UserInterace');
}
return match ($attribute) {
self::EDIT => $user === $subject || $this->security->isGranted('ROLE_SUPERADMIN'),
self::ENTITY_ACCESS => $this->security->isGranted('ROLE_SUPERADMIN'),
default => false,
};
}
Hey @CoachT ,
I'm happy to hear you found the solution! And thanks for sharing it with others, it might be helpful for someone!
Cheers!
I ran into the same error. It is because in UserCrudController.php we set parent::configureCrud($crud)->setEntityPermission('ADMIN_USER_EDIT')
and the AdminUserVoter only works if it is an existing UserEntity.
I could not yet figure out a working solution. I would be glab, If anyone could advice me here.
Great tutorial, but I'm starting with an empty Symfony project, I've added EasyAdmin, customized the dashboard, everything is fine, but I'm unable to find how to configure the database connection. And I'm probably unable to find this in the official EasyAdmin docs also... I know this tutorial is based on an existing project with pre-created entities and configuration, so which Symfony Cast should I subscribe to also to know how to connect to a database?
Hey @websafe,
Connecting to a database is pretty simple with Symfony. You only need to install Doctrine composer require symfony/orm-pack
then, override the connection environment variable like this: DATABASE_URL="mysql://root@127.0.0.1:3306/sfcasts_easyadmin"
You can read more about it here: https://symfony.com/doc/current/doctrine.html
Cheers!
Hello,
great tutorial!
I have one more question though. It would be great if you could help me.
I have overridden the createIndexQueryBuilder function in a CRUD controller. This works. However, I can't set a limit for the results.
public function __construct(EntityRepository $entityRepository)
{
$this->entityRepository = $entityRepository;
}
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): ORMQueryBuilder
{
$response = $this->entityRepository->createQueryBuilder($searchDto, $entityDto, $fields, $filters);
$response
->andWhere('entity.isDeleted = 1')
->setMaxResults($limit) // doesn't work?
;
return $response;
}
How can I limit the results?
Hey Thomas,
I think that's not how the "index query builder" is supposed to work because it uses a paginator behind the scenes. What you can do is to reduce/increase the number of items shown on a single page. You can do it like this:
class DashboardController extends AbstractDashboardController
{
public function configureCrud(): Crud
{
// ...
$crud->setPaginatorPageSize(5);
}
}
I have two tables: Authors and Posts. I was hoping to show a table with posts on the Auther's detail page. I can show a table through:
yield CollectionField::new('posts')
->setTemplatePath('admin/fields/posts.html.twig')
->onlyOnDetail();
And in that twig template, I can loop through the posts through: {% for post in field.value %}
However, I was hoping to make that table function like the tables EasyAdmin has on the Crud Index pages. Is there any way to neatly do this?
Hey WB,
Unfortunately, there's no way to do this... it's something custom you would need to implement yourself. That EA tables are tied to the EA interface and logic, so reuse it would be way to hard if ever possible I think. Probably could take a look at DataTables, see https://datatables.net/ - you can try to integrate them instead, it should be easier I think in case you don't want to render it like a simple list but add some functionality like sorting, search, etc. Otherwise you would need to implement it yourself.
I hope this helps!
Cheers!
Hello I need help, when i try to delete a user i get this error:
You cannot refresh a user from the EntityUserProvider that does not contain an identifier.
The user object has to be serialized with its own identifier mapped by Doctrine.
the strange thing is that sometimes works and on other occasions delete more than one user
Hey @Luis-MCM-925!
Hmm, that is strange. The EntityUserProvider
is a class that's involved with security. Its job is to, at the start of each request, load the curerntly-logged-in User
object from the session, grab its id
, and issue a query for a fresh version of that User
from the database.
What's strange about your situation is that, if you delete another user, it shouldn't affect YOUR user at all... and so your User should be refreshed from the session like normal. Based on the error, for some reason, it seems that, during the request to delete, YOUR User
object is losing its id before it's being stored in the session at the end of that request. It is almost like YOUR user is being deleted.
So, I can't explain what's going on - but maybe this will give you some hints.
Cheers!
Hi! I am following this tutorial, hoping that I can use it for a project I'm working it. Everythig's cool, but I can't figure out one thing:
Is the questions application open at least for reading or does it need the user to be registered before accessing to the questions&answers?
I am trying to create a simple app with the same philosofy as any blog: user can browse anonymously, but can post if registered and then admins use admin area, but I can't find the proper way to make it all in the same app: if I secure, everything needs login. If I open, no security is applied.
The maximum I got is unsecure the main page but then there is no chance to login and use user & session data in the main page.
How is it done here with symfony and easyadmin? it´s not such a weird case right? :)
Hey Tupolev,
In this chapter: https://symfonycasts.com/screencast/easyadminbundle/dashboard - we configured the minimal role that's required to access the EasyAdmin pages. As you can see, we configured it for ROLE_ADMIN, so not every user will have access the admin interface, only those who has that role. In your case, if you want to allow adding posts to all users who is registered and logged in on your website - you need to use the ROLE_USER probably. Everyone, who logged in to your site will have that simple (default) role: ROLE_USER - and so will have access to the admin interface and can create new posts. Though they will have access to remove as well unless you forbid removing manually. I would suggest to watch this course till the end coding along with the author to know more about EasyAdmin and its features to achieve this ;)
Cheers!
Cheers!
Got it! it worked with the course code. It was about the minimal role and the customAuthenticator. Now I have my public area and my admin area coexisting and the next challenge is creating a new dashboard for ROLE_USER so they can manage their own profile and CRUD places and pictures under their own ownership.
Is it a good approach?
-Public area (see places, register user)
-Admin console (manage all users, places, place_pictures and settings)
-Registered user console (manage own user profile, manage own places, place_pictures)
Thanks a lot!
Hey Tupolev
Registered user console (manage own user profile, manage own places, place_pictures)
Unless you mean to do this via EasyAdmin too - this sounds good. Because EasyAdmin is something internal and meant to be used by admins only. It has no nice URLs, etc. So, I would not recommend you to expose this to users just to edit their profiles. I would prefer to create a separate custom controller that will handle only this and keep the EasyAdmin help admins only.
Cheers!
Yeah a lot has happened in this time. I have implemented the public area and user panel alone and left easyadmin for admin. And it works great!
Thank you!
Hey Tupolev,
Awesome, that's the best solution I think :)
Cheers!
Hey Yoelkj,
You can override the getRedirectResponseAfterSave()
method in your custom CRUD controller and add the logic you need.
Cheers!
Hello, thanks for answering.
I have also seen that solution but I have not been able to override that method when it is protected. as I would do it
In the end I had to force it another way. I know it's not the best solution.
public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
parent::updateEntity($entityManager, $entityInstance);
$adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);
$url = $adminUrlGenerator
->setRoute('admin_show_user')
->generateUrl();
die(header('Location: '.$url));
}
Hey yoelkj,
I'm afraid I didn't get why you could not override a protected method. That's the purpose of the protected scope, to be overridden (and executed) by any of its derivates
Cheers!
Hello I'm having an issue trying to figure out rendering association fields as embedded forms in edit and new views.
The documentation here https://symfony.com/doc/4.x/EasyAdminBundle/fields/AssociationField.html
says that there is a method on AssociationField called renderAsEmbeddedForm() that is supposed to do exactly what I need, and I have the latest easyadmin version installed but that method does not exist...
I can't find anything on the internet about it missing either, the only google search result is the documentation page that says the method should be there.
Hey Nick!
Hmmm, yup! I see it! That feature is brand new - it has been merged - https://github.com/EasyCorp/EasyAdminBundle/pull/5353 - but not released yet. It should be part of 4.5.0 and Javier (the maintainer) is pretty good about not waiting too long to release. But if you need this now, you'll need to update your composer.json to use the 4.x branch.
Cheers!
Hey Christina,
It depends on what you're trying to achieve but I think a good way would be to override the createIndexQueryBuilder()
method
Cheers!
Hi Mollokan,
Indeed, my question was too "open", sorry for that.
Ok, so I'm gonna try to give some context.
I got this Registration entity, linked to some other entities (User, Activity ...).
Usually, in my CRUD controller, I'll use the default createIndexQueryBuilder, and in configureFields:
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id')
->onlyOnIndex(),
AssociationField::new('user')
AssociationField::new('activity'),
];
}
And I'll have, for the index page, a table with;
ID | user | activity
1 username1 activity_x
2 username4 activity_y
But the final user of the website I'm building wants something different for this INDEX page, to have easy access to the informations they need the most:
ID | Activity.title | Activity.startDate | User.firstname | User.lastname | User.phonenumber
So I need to "choose" some specific fields, for Association.
Is it more clear ?
Thanks !
Hey Christina,
You can access that information via Doctrine relationships. For example, if User has a relationship with Activity, you can access the Activity fields by doing something like this
public function configureFields(string $pageName): iterable
{
yield Field::new('activity.id', 'Activity ID');
}
Here you can learn more the EasyAdmin association field https://symfony.com/doc/current/EasyAdminBundle/fields/AssociationField.html
Cheers!
Hello,
your explanation is great. I want to extend this overriding possibility for my project.
I have 1 entity for article joined to another entity for images. 1 article can have many images.
when adding images to an article, I'd like to display the images and selecting them instead of just displaying their filename.
I have been trying with AssociationField but I can't get it.
Could you help me please ?
Symfony redirects to login page after successful login on chrome and logs out. This behavior is in some versions of chrome but not in Firefox, Edge or Safari although chrome does work in private mode and chrome (Version 105.0.5145.0) works even without private mode, any idea why login fails in chrome?
Hey Huib,
That's weird... especially the fact that it works in Chrome incognito mode but doesn't work in Chrome regular mode. It makes me think that you may have some extensions installed that causes this problem somehow. Most probably those Chrome extensions do not work in incognito mode (the default behavior) and that's why it works there - the only logical explanation about what's going on. I'd recommend you to turn off all the chrome extensions and try again. Also, try to upgrade your Chrome to the latest first. If the issue still persist - then I'm not sure. Well, you may want to open the Chrome dev toolbar, then long click on "reload" button (about 3 seconds) and choose "Empty cache and hard reload" - it might help sometimes.
I hope this helps!
Cheers!
Hey Lubna,
Thanks for the feedback! We are working to get it as soon as we can :-)
Thanks for staying with us!
Cheers!
26th December in five hours. We are waiting for! :)
Thank you for your casts, it's very helpful.
Hey Ruslan!
Sorry for some delays... Unfortunately, Christmas and new year schedule made some adjustments in the release schedule on SymfonyCasts, but this course will be the next for sure. Thank you for your patience! :)
Cheers!
Hello
Great tutorial. Thank you.
Please could you add an episode with
CollectionFields->useEntryCrudForm()
andAssociationFields->renderAsEmbeddedForm()
or how to embed a form from another Entity with a to-many Association or point to detailed documentation?
Or maybe that's the limit of easy admin?
Looking forward for your answer.
Thank you in advance.