List online users

If you are using FOSUserBundle or your own user system, it’s really easy to know which of your users are online.

The theary is simple, every user has a “lastActivity” proporety, and we will be using Symfony events to update this property on every request made by a user.

You start by adding this lastActivity property to your User class, its getter and setter, and for a more readable code, an “isActiveNow” method, wich as you may guess, will simply do “setLastActivity(new \DateTime())”.

Then create the following service :

namespace Acme\UserBundle\Listener;

use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Acme\UserBundle\Entity\User;

class ActivityListener
{
    protected $context;
    protected $em;

    public function __construct(SecurityContext $context, EntityManager $manager)
    {
        $this->context = $context;
        $this->em = $doctrine->getManager();
    }

    /**
    * Update the user "lastActivity" on each request
    * @param FilterControllerEvent $event
    */
    public function onCoreController(FilterControllerEvent $event)
    {
        // Here we are checking that the current request is a "MASTER_REQUEST", and ignore any subrequest in the process (for example when doing a render() in a twig template)
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        // We are checking a token authentification is available before using the User
        if ($this->context->getToken()) {
            $user = $this->context->getToken()->getUser();

            // We are using a delay during wich the user will be considered as still active, in order to avoid too much UPDATE in the database
            $delay = new \DateTime();
            $delay->setTimestamp(strtotime('2 minutes ago'));

            // We are checking the User class in order to be certain we can call "getLastActivity".
            if ($user instanceof User && $user->getLastActivity() < $delay) {
                $user->isActiveNow();
                $this->em->flush($user);
            }
        }
    }
}

Then you need you need to add the service in the dependancy injection configuration, by adding it to “Acme/UserBundle/Resources/config/services.yml” (and making sure you are loading this configuration file in Acme\UserBundle\DependencyInjection\AcmeUserExtension) :

services:
    activity_listener:
        class: Acme\UserBundle\Listener\ActivityListener
        arguments: [@security.context, @doctrine]
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }

When declaring our service, we tag it as an Event Listener on the “kernel.controller” event, when will then execute our “onCoreController” method.
We alson inject the “security.context” and “doctrine” service.

You now need to read the informations, starting by adding a method to the UserRepository (when using Doctrine ORM) :

namespace Acme\UserBundle\Entity;

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
	public function getActive()
	{
		// As may have notice, the delay is redondant, for the sake of the example, i didn't defined its value in my bundle configuration, but it would be better to do so.
		$delay = new \DateTime();
		$delay->setTimestamp(strtotime('2 minutes ago'));

		$qb = $this->createQueryBuilder('u')
			->where('u.lastActivity > :delay')
			->setParameter('delay', $delay)
		;

		return $qb->getQuery()->getResult();
	}
}

(Don’t forget if not already done, to specify the User entity to use the custom repository : @ORM\Entity(repositoryClass=”UserRepository”))

Then a simple controller :

namespace Acme\UserBundle\Controller;

class UserController extends Controller
{
    /**
     * @Template()
     */
    public function whoIsOnlineAction()
    {
    	$users = $this->getDoctrine()->getManager()->getRepository('AcmeUserBundle:User')->getActive();

    	return array('users' => $users);
    }
}

And a tiny template :

<p id="who-is-online">
	<strong>Utilisateurs en ligne ({{ users|length }}) :</strong> {{ users|join(', ') }}.
</p>

And voilà, now you simply have to render your action wherever you want to list your online users.

{{ render(controller('AcmeUserBundle:User:whoIsOnline')) }}

Versions used : 2.2, 2.3

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>