Lister les utilisateurs en ligne

Que vous utilisiez FOSUserBundle ou votre propre système de gestion d’utilisateurs, il est très facile d’ajouter la possibilité de connaître les utilisateurs qui sont actuellement en ligne.

Le principe est simple, chaque utilisateur possède un attribut “lastActivity” et nous utilisons les évènements de Symfony pour mettre à jour cet attribut lorsqu’une requête est demandée.

Vous devez donc commencer par ajouter un attribut lastActivity sur votre class User, ainsi qu’un getter, setter, et pour plus de simplicité, une méthode “isActiveNow” qui fera un “setLastActivity(new \DateTime())”.

Ensuite, créez le service suivant :

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 = $manager;
    }

    /**
    * Update the user "lastActivity" on each request
    * @param FilterControllerEvent $event
    */
    public function onCoreController(FilterControllerEvent $event)
    {
        // ici nous vérifions que la requête est une "MASTER_REQUEST" pour que les sous-requête soit ingoré (par exemple si vous faites un render() dans votre template)
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        // Nous vérifions qu'un token d'autentification est bien présent avant d'essayer manipuler l'utilisateur courant.
        if ($this->context->getToken()) {
            $user = $this->context->getToken()->getUser();

            // Nous utilisons un délais pendant lequel nous considèrerons que l'utilisateur est toujours actif et qu'il n'est pas nécessaire de refaire de mise à jour
            $delay = new \DateTime();
            $delay->setTimestamp(strtotime('2 minutes ago'));

            // Nous vérifions que l'utilisateur est bien du bon type pour ne pas appeler getLastActivity() sur un objet autre objet User
            if ($user instanceof User && $user->getLastActivity() < $delay) {
                $user->isActiveNow();
                $this->em->flush($user);
            }
        }
    }
}

Ensuite il faut ajouter votre service à l’injecteur de dépendance, donc dans Acme/UserBundle/Resources/config/services.yml (en vérifiant que vous exploitez bien ce yml dans Acme\UserBundle\DependencyInjection\AcmeUserExtension) :

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

On déclare donc notre service en le taguant comme Event Listener, sur l’event “kernel.controller” et on y exécutera notre méthode onCoreController tout en injectant au service le security context et doctrine.

Il ne reste ensuite plus qu’à faire la “lecture”, en commençant par la méthode du UserRepository (cas en Doctrine ORM) :

namespace Acme\UserBundle\Entity;

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
	public function getActive()
	{
		// Comme vous le voyez, le délais est redondant ici, l'idéale serait de le rendre configurable via votre bundle
		$delay = new \DateTime();
		$delay->setTimestamp(strtotime('2 minutes ago'));

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

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

(N’oubliez pas, si ce n’est pas déjà fait, de préciser à votre User d’utiliser ce Repository : @ORM\Entity(repositoryClass=”UserRepository”))

Ensuite, une petite action de controller toute simple :

namespace Acme\UserBundle\Controller;

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

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

Un petit template :

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

Et voilà, il ne vous reste plus qu’à insérer votre action où vous voulez !

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

Versions utilisées : 2.2, 2.3

Leave a Reply to Tony Cancel 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>