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.

Start your All-Access Pass
Buy just this tutorial for $8.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

When 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.

Leave a comment!

This tutorial is built on an older version of Symfony & EasyAdminBundle. Many of the concepts are the same, but you can expect major differences in newer versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.7
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.17.0
        "sensio/distribution-bundle": "^5.0", // v5.0.25
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.4
        "knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "stof/doctrine-extensions-bundle": "^1.2", // v1.3.0
        "javiereguiluz/easyadmin-bundle": "^1.16" // v1.17.21
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.7
        "symfony/phpunit-bridge": "^3.0", // v3.4.40
        "nelmio/alice": "^2.1", // v2.3.5
        "doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
    }
}