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

11 réflexions au sujet de « Lister les utilisateurs en ligne »

  1. ProPheT777

    Salut,

    Déjà sympa le tutoriel, je pense que sa aidera pas mal de monde !

    Juste un petit conseil, au niveau des services du listener tu injecte @doctrine, alors que tu n’utilise que son manager $doctrine->getManager(). Dans se cas il mieux d’injecter @doctrine.entity_manager qui est le bon service (Dans le principe c’est exactement pareil que ne pas appeler le service @container mais uniquement les services que l’on utilise) ;)

    @+

    Répondre
    1. Esmedo

      Re,

      Je ne suis pas encore très compétent sur les services&évènements, mais je me demandais s’il n’était pas possible de définir « 2 minutes ago' » en parameter et du coup le passer en argument?

      Répondre
  2. Corgato

    Bonjour,
    merci pour ton tuto !
    Je ne comprends pas un des premiers points :

    et pour plus de simplicité, une méthode « isActiveNow » qui fera un « setLastActivity(new \DateTime()) ».

    Pourrais tu m’éclaircir :)
    Merci !

    Répondre
    1. Jcrombez Auteur de l’article

      C’est simplement une méthode qui permet de définir la date de dernière connexion d’un utilisateur à la date/heure actuelle, de manière plus simple.

      Répondre
  3. Tony

    Amazing post! A quick suggestion to say that it might be easier to read the code if isActiveNow() returns a boolean of whether the user is active now. Since the setter is used only once, I’d still use setLastActivity to update the activity. Just a thought.

    Répondre
    1. Jcrombez Auteur de l’article

      You’re right, the method « isActiveNow » sounds like a question.
      When i decided this name, i was thinking more something like « user is active now. » but maybe i should have choose « isNowActive » or something like that.

      Répondre
  4. Esmedo

    Salut :))

    Je voulais juste te remericier pour ce tuto. Je n’aurais pas réussi tout seul!

    Juste dans le service j’ai mis @doctrine.orm.entity_manager.

    @+++

    Répondre
    1. Esmedo

      Re,

      Il est possible de passer ‘2 minutes ago’ en argument en le définissant en tant que paramètre. Comme cela on peut le changer directement au même endroit.

      J’ai lu dans un cours qu’il fallait utiliser la méthode getName obligatoirement dans son service lorsque l’on utilise les tags. Dans le cours il parle d’un service affilier à twig… Dans notre cas, ça serait :

      public function getName()
      {
      return ‘ActivityListener';
      }

      Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>