Buy

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:

281 lines src/AppBundle/Entity/User.php
... 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(), perPersistEntity() 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:

18 lines src/AppBundle/Controller/EasyAdmin/UserController.php
... 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:

18 lines src/AppBundle/Controller/EasyAdmin/UserController.php
... 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()):

18 lines src/AppBundle/Controller/EasyAdmin/UserController.php
... 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:

199 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
... lines 99 - 181
User:
... line 183
controller: AppBundle\Controller\EasyAdmin\UserController
... lines 185 - 199

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:

27 lines src/AppBundle/Controller/EasyAdmin/AdminController.php
... 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:

10 lines src/AppBundle/Controller/EasyAdmin/AdminController.php
... 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:

25 lines src/AppBundle/Controller/EasyAdmin/GenusController.php
... 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:

25 lines src/AppBundle/Controller/EasyAdmin/GenusController.php
... lines 1 - 2
namespace AppBundle\Controller\EasyAdmin;
class GenusController extends AdminController
{
... lines 7 - 23
}

Repeat that in UserController:

17 lines src/AppBundle/Controller/EasyAdmin/UserController.php
... 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:

200 lines app/config/config.yml
... lines 1 - 80
easy_admin:
... lines 82 - 97
entities:
Genus:
... line 100
controller: AppBundle\Controller\EasyAdmin\GenusController
... lines 102 - 200

Now we're setup to do some really, really cool stuff.

Leave a comment!

  • 2017-08-11 Diego Aguiar

    Oh I got you now, I'm not an expert using EasyAdminBundle, but I think you will have to put in practice almost everything you have learned in this tutorial, like override a layout, a form, controller action, etc.

    After doing it, you will be able to do anything you want in EasyAdminBundle :)

    Cheers!

  • 2017-08-10 Riccardo Previti

    Thank you for your time. You're exactly talking about what I want. I already did the tutorial and the relation is already set up. Maybe I did not express myself well, but I am only concerned about how to make it look like a many to many in easyadmin's backend entity. For now, it only tries to list joinTable entities. Those should not be choosen, but generated when choosing the to-be-related entity. Example:

    Many Questions can have many Options.

    In my backend I want to create questions and assign options to it or create options and see which questions they belong to. Can't find any documentation anywhere on this

  • 2017-08-07 Diego Aguiar

    Hey @Riccardo Previti

    Usually when you have a many-to-many relationship you don't need an associated entity, but when you need custom fields for that relation (let's say, createdAt), then you will have to create a new entity that will hold those values and a reference to the related entities. In this case you will end up with 3 classes with a one-to-many relationship.
    You can find good examples here: http://codemonkeys.be/2013/...

    Also you may find helpful this tutorial to start working with associations: http://knpuniversity.com/sc...

    Have a nice day!

  • 2017-08-05 Riccardo Previti

    Hi :-)
    I'm trying to get a smooth admin interface similar as if when two entities are associated by a many-to-many relationship. I need the join table to define additional information like rank. I don't want to display the jointable entity on the backend, it should be writeable in the edit-menu of at least one entity. How can I make an edit form access the values of the jointable?