Whilst working on a personal project based on the Symfony2 framework I wanted to record when a user was last active so that I could emulate a list of currently online users. I had already plugged in the amazing FOSUserBundle extension, so this was just about tweaking what was already there. If you’re in a similar situation and already have the Symfony2 standard distribution installed with FOSUserBundle then this walkthrough should be pretty straightforward.
Utilising Symfony2′s Event Model
Symfony2 has a great plethora of internal events that you can ‘hook’ into. In our case, we want to hook into a kernel event called which will fire whenever a request is made by the client browser. In your User bundle, create a services.yml file (e.g. src/App/UserBundle/Resources/config/services.yml) as follows:
services:
activity_listener:
class: App\UserBundle\Listener\Activity
arguments: [@security.context, @doctrine]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onCoreController }
This is quite straightforward, this is just telling our application that when the kernel.controller event is fired we want our kernel to also call the onCoreController() method in our App\UserBundle\Listener\Activity class (we’ll create that in a minute). Before we move on to creating our event handler class we need to register our services.yml file with our main kernel. We can do this by editing app/config/config.yml and adding an import directive at the top of the file…
imports:
- { resource: @AppUserBundle/Resources/config/services.yml }
The Event Handler Class
Now we have to create our event handler class, which will do most of the work. I decided to call my class Activity, but you can call it whatever you like as long as you make sure to amend your services.yml file to contain the same name. Our file will reside at src/UserBundle/Listener/Activity.php…
namespace App\UserBundle\Listener;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Bundle\DoctrineBundle\Registry as Doctrine;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use DateTime;
use App\UserBundle\Entity\User;
class Activity
{
protected $context;
protected $em;
public function __construct(SecurityContext $context, Doctrine $doctrine)
{
$this->context = $context;
$this->em = $doctrine->getEntityManager();
}
/**
* On each request we want to update the user's last activity datetime
*
* @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
* @return void
*/
public function onCoreController(FilterControllerEvent $event)
{
$user = $this->context->getToken()->getUser();
if($user instanceof User)
{
//here we can update the user as necessary
$user->setLastActivity(new DateTime());
$this->em->persist($user);
$this->em->flush($user);
}
}
}
Be sure to update your User class with the correct properties (e.g. last_activity) in order for the setters to work and you’re away.
8 Comments
Need change onCoreController method, because in dev environment profiler debug bar has error “Call to a member function getUser() on a non-object”.
I add this clause
if ($this->context->getToken())
{
$user = $this->context->getToken()->getUser();
//…
}
Sorry for my english.
Excellent spot! The token only becomes active after we have logged in, so calling getUser() before logging in would indeed cause an error.
I have new changes
If you use partial controller like {% render %} you call onCoreController each time and have many sql inserts, need add this clouse
if ($event->getRequestType() !== \Symfony\Component\HttpKernel\HttpKernel::MASTER_REQUEST) {
return;
};
[...] Recording ‘Last Activity’ for Users in Symfony2 + FOSUserBundle [...]
Thanks for the tip. I’ll try it on my website as soon as possible.
One question though, why are you persisting the user on each request ? The $this->em->flush() should be enough, no ?
It probably doesn’t need the persist operation on every request, but because we’re not loading the user via Doctrine, its minimal overhead to have that line in and make sure that it gets persisted. Let me know if you find this not to be the case.
Thank you for your tutorial, it’s great.
İ need to log other informations in log file, and for this i add monolog service. But when i get my service like this:
$this->get(‘admin.logger’);
he say:
Fatal error: Call to undefined method Enuygun\UserBundle\Listener\Activity::get()
But i can call this service from other controllers, what i neet add to use my monolog service here (in onCoreController)?
i add my monolog service like that in services.yml:
admin.logger:
class: Symfony\Bridge\Monolog\Logger
arguments: [app]
calls:
– [pushHandler, [@admin.logger_handler]]
admin.logger_handler:
class: Monolog\Handler\StreamHandler
arguments: [%kernel.logs_dir%/%kernel.environment%.admin.log, 200]
(Sorry for my english)
Hello
This is very useful solution for me, thank you very much for this.
But I got an error, when I wanted to use (symfony 2.1):
ErrorException: Catchable Fatal Error: Argument 2 passed to …\Bundle\Listener\Activity::__construct() must be an instance of Symfony\Bundle\DoctrineBundle\Registry, instance of Doctrine\Bundle\DoctrineBundle\Registry given, called in ….
so I needed to change this row:
use Symfony\Bundle\DoctrineBundle\Registry as Doctrine
to this:
use Doctrine\Bundle\DoctrineBundle\Registry as Doctrine
thanks once again
hs