Permalink

12

Calling a Method Before Every Controller Action in Symfony2

There are times when you need to execute a method on a controller before every action and sometimes on multiple controllers. In my case I had to check that a user was associated with a particular company and if so, fetch the company and some related data from the database. This had to happen before every action in the controller. It also had to happen in several controllers. Rather than extending a common controller class and calling a method from every action I decided to imitate the old symfony1 behavior of the preExecute method.


First, I created an interface. I wanted to be able to implement this interface on any controller that needed to be initialized without adding any extra code or configuration. In my case I always need access to the request and the security context so I specified that here.

1 2 3 4 5 6 7 8 9 10 11 12 13 14
<?php
 
namespace SOTB\CoreBundle\Model;
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
 
/**
* @author Matt Drollette <[email protected]>
*/
interface InitializableControllerInterface
{
public function initialize(Request $request, SecurityContextInterface $security_context);
}

Next I implemented this interface on one of my controllers. In it, I get the user and assign to a property. I also check that the user is assigned to a company and throw an error otherwise.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
<?php
 
namespace SOTB\CustomerBundle\Controller;
 
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
 
use SOTB\CoreBundle\Model\InitializableControllerInterface;
 
/**
* @author Matt Drollette <[email protected]>
*/
class CustomerController implements InitializableControllerInterface
{
private $user;
private $company;
 
public function initialize(Request $request, SecurityContextInterface $security_context)
{
$this->user = $security_context->getToken()->getUser();
$this->company = $this->user->getCompany();
 
if (!$this->company) {
throw new NotFoundHttpException('You are not assigned to a company.');
}
}
 
// ... rest of controller actions
}

The next step is to create an event listener that checks the controller being called to see if it implements our initializable interface and if so to execute the initialize method.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
<?php
 
namespace SOTB\CoreBundle\EventListener;
 
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use SOTB\CoreBundle\Model\InitializableControllerInterface;
 
/**
* @author Matt Drollette <[email protected]>
*/
class BeforeControllerListener
{
private $security_context;
 
public function __construct(SecurityContextInterface $security_context)
{
$this->security_context = $security_context;
}
 
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
 
if (!is_array($controller)) {
// not a object but a different kind of callable. Do nothing
return;
}
 
$controllerObject = $controller[0];
 
// skip initializing for exceptions
if ($controllerObject instanceof ExceptionController) {
return;
}
 
if ($controllerObject instanceof InitializableControllerInterface) {
// this method is the one that is part of the interface.
$controllerObject->initialize($event->getRequest(), $this->security_context);
}
}
}

We now have to attach that event listener to the kernel.controller event.

1 2 3 4 5 6 7 8 9 10 11 12 13
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
<services>
<service id="sotb_core.listener.before_controller" class="SOTB\CoreBundle\EventListener\BeforeControllerListener" scope="request">
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
<argument type="service" id="security.context"/>
</service>
</services>
 
</container>
view raw services.xml hosted with ❤ by GitHub

Now any time we need to execute a method before every action in a controller we simply implement the InitializableControllerInterface.

Author: Matt Drollette

I am a software developer in Dallas, TX.

12 Comments

  1. Have I to implement the initialize function inside every Controller? or there are a way to implement this method just one time?

    It occurs to me something like put the logic in a class and call it instead of the initialize function, it could be correct?

    • If you have several controllers that need the exact same initialize method then I would create a CoreController class that implements that interface and then extend that in the others.

  2. Thank you for your post!

    Your article is very helpful. I wrote my own ACL in a bundle for Sf2 and before execute the action in a controller, I need to check if the user has the necessary privileges and with this Listener I can do that :)

  3. On Symfony 2.2, I’m doing all the logic of the initialize() function right there where you have the call, so I’ll have something like this:

    if ($controllerObject instanceof InitializableControllerInterface) {
    $controllerObject->user = $this->security_context->getToken()->getUser();
    $controllerObject->company = $controllerObject->user->getCompany();

    if (!$controllerObject->company) {
    throw new NotFoundHttpException(‘You are not assigned to a company.’);
    }
    }

    Of course, the InitializableControllerInterface is not really used in this case, or at least it should be just empty (a Marker Interface), without the initialize method. All controllers implementing this interface will get the user and company properties initialized.

    Is this a bad practice at all or is it just the same idea?

    • This sounds fine. There’s tradeoffs, you can’t access private/protected methods of your controller from the listener whereas with the initialize method you can. But if you just need to call some public methods or set some public properties on the controller then your way works.

      • Thanks buddy. I’m thinking of refactoring a little bit using some of your code.

        Basically, my idea is, having an unique BeforeControllerListener in my CoreBundle, and then make use of that service in all other bundles/projects, looking to avoid the same kind of Listener in all our project/bundles.

        So, I can have on my CoreBundle Interfaces defining the contract/signature for each one of my Projects/Bundles Base Controllers, and then on each bundle/project I can have a BundleXBaseController abstract class implementing the specific interface for BundleX and implementing there the initialize() method with the proper parameters and some other functionality for all Controllers on BundleX.

        The Listener will look what kind of controller it is (is instance of XYZ Interface), and then will call initialize() with the specific parameters for that type of Controller.

        What do you think of this?

Leave a Reply

Required fields are marked *.