Override Controllers
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWhen we submit a form, obviously, EasyAdminBundle is taking care of everything: handling validation, saving and adding a flash message. That's the best thing ever! Until... I need to hook into that process... then suddenly, it's the worst thing ever! What if I need to do some custom processing right before an entity is created or updated?
There are 2 main ways to hook into EasyAdminBundle...and I want you to know both. Open the User
entity. It has an updatedAt
field:
// ... lines 1 - 16 | |
class User implements UserInterface | |
{ | |
// ... lines 19 - 82 | |
/** | |
* @ORM\Column(name="updated_at", type="datetime", nullable=true) | |
*/ | |
private $updatedAt; | |
// ... lines 87 - 279 | |
} |
To set this, we could use Doctrine lifecycle callbacks or a Doctrine event subscriber.
But, I want to see if we can set this instead, by hooking into EasyAdminBundle. In other words, when the user submits the form for an update, we need to run some code.
The protected AdminController Methods
The first way to do this is by adding some code to the controller. Check this out: open up the base AdminController
from the bundle and search for protected function
. Woh... There are a ton of methods that we can override, beyond just the actions. Like, createNewEntity()
, prePersistEntity()
and preUpdateEntity()
.
If we override preUpdateEntity()
in our controller, that will be called right before any entity is updated. There are a few other cool things that you can override too.
Per-Entity Override Methods
Ok, easy! Just add preUpdateEntity()
to our AdminController
, right? Yep... but we can do better! If we override preUpdateEntity()
, it will be called whenever any entity is updated. But we really only want it to be called for the User
entity.
Once again, EasyAdminBundle has our back. Inside the base controller, search for preUpdate
. Check this out: right before saving, it calls some executeDynamicMethod
function and passes it preUpdate
, a weird <EntityName>
string, then Entity
.
Actually, the bundle does this type of thing all over the place. Like above, when it calls createEditForm()
. Whenever you see this, it means that bundle will first look for an entity-specific version of the method - like preUpdateUserEntity()
- and call it. If that doesn't exist, it will call the normal preUpdateEntity()
.
This is huge: it means that each entity class can have its own set of hook methods in our AdminController
!
One Controller per Entity
And now that I've told you that... we're going to do something completely different. Instead of having one controller - AdminController
- full of entity-specific hook methods like preUpdateUserEntity
or createGenusEditForm
- I prefer to create a custom controller class for each entity.
Try this: in the EasyAdmin
directory, copy AdminController
and rename it to UserController
. Then, remove the function. Use the "Code"->"Generate menu" - or Command
+N
on a Mac - to override the preUpdateEntity()
method. And don't forget to update your class name to UserController
:
// ... lines 1 - 2 | |
namespace AppBundle\Controller\EasyAdmin; | |
use AppBundle\Entity\User; | |
use JavierEguiluz\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController; | |
class UserController extends BaseAdminController | |
{ | |
/** | |
* @param User $entity | |
*/ | |
protected function preUpdateEntity($entity) | |
{ | |
// ... line 15 | |
} | |
} |
We're going to configure things so that this UserController
is used only for the User
admin section. And that means we can safely assume that the $entity
argument will always be a User
object:
// ... lines 1 - 4 | |
use AppBundle\Entity\User; | |
// ... lines 6 - 7 | |
class UserController extends BaseAdminController | |
{ | |
/** | |
* @param User $entity | |
*/ | |
protected function preUpdateEntity($entity) | |
{ | |
// ... line 15 | |
} | |
} |
And that makes life easy: $entity->setUpdatedAt(new \DateTime())
:
// ... lines 1 - 7 | |
class UserController extends BaseAdminController | |
{ | |
// ... lines 10 - 12 | |
protected function preUpdateEntity($entity) | |
{ | |
$entity->setUpdatedAt(new \DateTime()); | |
} | |
} |
But how does EasyAdminBundle
know to use this controller only for the User
entity? That happens in config.yml
. Down at the bottom, under User
, add controller:
AppBundle\Controller\EasyAdmin\UserController:
// ... lines 1 - 80 | |
easy_admin: | |
// ... lines 82 - 97 | |
entities: | |
// ... lines 99 - 158 |
And just like that! We have one controller that's used for just our User
.
Try it out! Let's go find a user... how about ID 20. Right now, its updateAt
is null. Edit it... make some changes... and save! Go back to show and... we got it!
Organizing into a Base AdminContorller
This little trick unlocks a lot of hook points. But if you look at AdminController
, it's a little messy. Because, changePublishedStatusAction()
is only meant to be used for the Genus
class:
// ... lines 1 - 6 | |
class AdminController extends BaseAdminController | |
{ | |
public function changePublishedStatusAction() | |
{ | |
$id = $this->request->query->get('id'); | |
$entity = $this->em->getRepository('AppBundle:Genus')->find($id); | |
// ... lines 13 - 24 | |
} | |
} |
But technically, this controller is being used by all entities, except User
.
So let's copy AdminController
and make a new GenusController
! Empty AdminController
completely:
// ... lines 1 - 2 | |
namespace AppBundle\Controller\EasyAdmin; | |
use JavierEguiluz\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController; | |
class AdminController extends BaseAdminController | |
{ | |
} |
Then, make sure you rename the new controller class to GenusController
:
// ... lines 1 - 2 | |
namespace AppBundle\Controller\EasyAdmin; | |
class GenusController extends AdminController | |
{ | |
public function changePublishedStatusAction() | |
{ | |
$id = $this->request->query->get('id'); | |
$entity = $this->em->getRepository('AppBundle:Genus')->find($id); | |
$entity->setIsPublished(!$entity->getIsPublished()); | |
$this->em->flush(); | |
$this->addFlash('success', sprintf('Genus %spublished!', $entity->getIsPublished() ? '' : 'un')); | |
return $this->redirectToRoute('easyadmin', [ | |
'action' => 'show', | |
'entity' => $this->request->query->get('entity'), | |
'id' => $id, | |
]); | |
} | |
} |
But before we set this up in config, change the extends to extends AdminController
, and remove the now-unused use
statement:
// ... lines 1 - 2 | |
namespace AppBundle\Controller\EasyAdmin; | |
class GenusController extends AdminController | |
{ | |
// ... lines 7 - 23 | |
} |
Repeat that in UserController
:
// ... lines 1 - 2 | |
namespace AppBundle\Controller\EasyAdmin; | |
// ... lines 4 - 6 | |
class UserController extends AdminController | |
{ | |
// ... lines 9 - 15 | |
} |
Yep, now all of our sections share a common base AdminController
class. And even though it's empty now, this could be really handy later if we ever need to add a hook that affects everything.
Love it! UserController
has only the stuff it needs, GenusController
holds only things that relate to Genus
, and if we need to override something for all entities, we can do that inside AdminController
.
Don't forget to go back to your config to tell the bundle about the GenusController
. All the way on top, set the Genus
controller to AppBundle\Controller\EasyAdmin\GenusController
:
// ... lines 1 - 80 | |
easy_admin: | |
// ... lines 82 - 97 | |
entities: | |
Genus: | |
// ... line 100 | |
controller: AppBundle\Controller\EasyAdmin\GenusController | |
// ... lines 102 - 159 |
Now we're setup to do some really, really cool stuff.
Hi,
It's so weird for me that just when I add "EasyAdmin" folder at my bundle, I get the message that:
No route found for "GET /admin/"
I've just check any possible solutions without any success :(
Any idea?