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):
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?
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.