1
votes

My user has countTasks property, with corresponding setter and getter:

class User implements UserInterface, \Serializable
{
    /**
     * @var integer
     */
    private $countTasks;
}

I want this property to be always shown in the application's navigation bar (the "14" number in red):

nav bar with countTasks property

Obviously, this property should be set for every controller. (Actually, only for every that deals with rendering the navigation bar, but that's not the case here). So the application should count tasks for the currently logged-in user for every controller.

I found a relevant topic in the Symfony cookbook: How to Setup before and after Filters, and I managed to implement it:

Acme\TestBundle\EventListener\UserListener.php:

namespace Acme\TestBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class UserListener
{
    public function onKernelController(FilterControllerEvent $event)
    {
        $controller = $event->getController();

        if ( ! is_array($controller)) {
            return;
        }

        $securityContext = $controller[0]->get('security.context');

        // now count tasks, but only if a user logged-in
        if ($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') or $securityContext->isGranted('IS_AUTHENTICATED_FULLY'))
        {
            $user = $securityContext->getToken()->getUser();

            // ...
            // countig tasks and setting $countTasks var
            // ...
            $user->setCountTasks($countTasks);
        }
    }
}

services.yml:

services:
    acme.user.before_controller:
        class: Acme\TestBundle\EventListener\UserListener
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

It works as expected and I'm able to pull the property in a Twig template like this:

{{ app.user.countTasks }}

It works as expected in prod env.
In dev however, profiler throws UndefinedMethodException:

UndefinedMethodException: Attempted to call method "get" on class "Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController" in ...\src\Acme\TestBundle\EventListener\UserListener.php line 18.

where line 18 is this one:

$securityContext = $controller[0]->get('security.context');

As a quick patch I added additional check (before line 18) to prevent profiler from executing the further logic:

    if (is_a($controller[0], '\Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController'))
    {
        return;
    }

    $securityContext = $controller[0]->get('security.context');

and it has made the trick. But I'm afraid it's not the right way. I'm also afraid that I'm loosing some part of debug information in profiler.

Am I right with my concerns? Can you point me to a better way to prevent profiler from executing this listener? In config somehow?

2

2 Answers

2
votes

Even in Symfony's documentation How to Setup before and after Filters, an instanceof condition is being evaluated in line 29.

I'd go about saying that if the doc's doing it, you're pretty safe doing it yourself (unless stated otherwise, which is not the case).

1
votes

In the beginning I was trying to fix the issue conversely than I should. During testing it turned out that I have to exclude some other core controllers too. Of course, rather than block unwanted controllers I should have allowed the required ones only.

I ended up creating an empty interface UserTasksAwareController:

namespace Acme\TestBundle\Controller;

interface UserTasksAwareController
{}

fixing that validity check in UserListener.php:

if ( ! $controllers[0] instanceof UserTasksAwareController) {
    return;
}

and implementing it in every other controller which deals with displaying countTasks property, like this one:

class UserController extends Controller implements UserTasksAwareController

So, the problem I had was just another one when you forget to program to an interface, not an implementation.