13

I want to return all Logged in users of my application and render it in my Dashboard. The user_id and user_name should be retrieved from the session (I am using an external LDAP Library for authentication)

I have created a field in the database called lastActivity which will contain the last login time and then I can query the database for lastActivity display users logged in in the last 2 minutes.

ActivityListener.php

     <?php

namespace Bnpp\SecurityBundle\EventListener;

use Doctrine\ORM\EntityManager;
//use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Acme\SecurityBundle\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Registry;


/**
 * Listener that updates the last activity of the authenticated user
 */

class ActivityListener

    {
    protected $securityContext;
    protected $entityManager;

    public function __construct(SecurityContext $securityContext, EntityManager $entityManager)
    {
        $this->securityContext = $securityContext;
        $this->entityManager = $entityManager;
    }



    /**
     * Update the user "lastActivity" on each request
     * @param FilterControllerEvent $event
     */


    public function onCoreController(FilterControllerEvent $event)
    {

        // Check that the current request is a "MASTER_REQUEST"
        // Ignore any sub-request
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        // Check token authentication availability
        if ($this->securityContext->getToken()) {
            $user = $this->securityContext->getToken()->getUser();


            if ( ($user instanceof User) && !($user->isActiveNow()) ) {
                $user->setLastActivity(new \DateTime('now'));
                $this->entityManager->flush($user);
            }
        }

    }

}

Services.yml

services:
    activity_listener:
        class: Bnpp\SecurityBundle\EventListener\ActivityListener
        arguments: [@security.context, @doctrine.orm.entity_manager]
        tags:
         - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }

User Entity

<?php

namespace Acme\SecurityBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * User
 *
 * @ORM\Table(name="users")
 * @ORM\Entity(repositoryClass="Acme\SecurityBundle\Entity\UserRepository")
 */
class User implements UserInterface
{

/**
     * @var \DateTime
     * @ORM\Column(name="LASTACTIVITY", type="datetime")
     */
    private $lastActivity;


    /**
     * @return bool whether the user is active or not
     */

    public function isActiveNow()
     {

    $delay = new\DateTime('2 minutes ago');

    return($this->getlastActivity()>$delay);

       }

/**
     * Set lastActivity
     *
     * @param\Datetime $lastActivity
     * @return User
     */


    public function setlastActivity($lastActivity)
    {
        $this->lastActivity = $lastActivity;

        return $this;
    }


    /**
     * Get lastActivity
     *
     * @return \DateTime
     */
    public function getlastActivity()
    {
        return $this->lastActivity;
    }




}
Mick
  • 30,759
  • 16
  • 111
  • 130
StarJedi
  • 1,410
  • 3
  • 21
  • 34
  • is there a question here? – Sehael Jan 13 '14 at 17:12
  • the session isnt global for all users; in this case you should query your DB and select all the users who logged_in_time >= now()-5 minutes – Flask Jan 13 '14 at 17:59
  • 1
    If you are saving session data in the database, you could just get the list of active sessions from the DB and assume those users are logged in. – Petar Zivkovic Jan 13 '14 at 19:49
  • @Petar Zivkovic - I created a table which logs in the user_id and last_login_time. How would I query the database to display logged_in_time >= now()-5 minutes? Can you illustrate it with example code? Thanks – StarJedi Jan 29 '14 at 00:39

5 Answers5

26

There is a great post here: List online users.

You can create a Listener that listens on the kernel.controller event and updates a user field lastActivity every time a user is active. You can check lastActivity < now()- 2 minutes and update lastActivity timestamp.

Also: Implementing user activity in symfony 2

Here is how to do it

Note: If you're not using FOSUserBundle, see Edit below.

1 Add this to your User Entity

/**
 * Date/Time of the last activity
 *
 * @var \Datetime
 * @ORM\Column(name="last_activity_at", type="datetime")
 */
protected $lastActivityAt;

/**
 * @param \Datetime $lastActivityAt
 */
public function setLastActivityAt($lastActivityAt)
{
    $this->lastActivityAt = $lastActivityAt;
}

/**
 * @return \Datetime
 */
public function getLastActivityAt()
{
    return $this->lastActivityAt;
}

/**
 * @return Bool Whether the user is active or not
 */
public function isActiveNow()
{
    // Delay during wich the user will be considered as still active
    $delay = new \DateTime('2 minutes ago');

    return ( $this->getLastActivityAt() > $delay );
}

2 Create Event Listener

<?php
namespace Acme\UserBundle\EventListener;

use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Model\UserInterface;

/**
 * Listener that updates the last activity of the authenticated user
 */
class ActivityListener
{
    protected $securityContext;
    protected $userManager;

    public function __construct(SecurityContext $securityContext, UserManagerInterface $userManager)
    {
        $this->securityContext = $securityContext;
        $this->userManager = $userManager;
    }

    /**
    * Update the user "lastActivity" on each request
    * @param FilterControllerEvent $event
    */
    public function onCoreController(FilterControllerEvent $event)
    {
        // Check that the current request is a "MASTER_REQUEST"
        // Ignore any sub-request
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        // Check token authentication availability
        if ($this->securityContext->getToken()) {
            $user = $this->securityContext->getToken()->getUser();

            if ( ($user instanceof UserInterface) && !($user->isActiveNow()) ) {
                $user->setLastActivityAt(new \DateTime());
                $this->userManager->updateUser($user);
            }
        }
    }
}

3 Declare event Listener as a service

parameters:
    acme_user.activity_listener.class: Acme\UserBundle\EventListener\ActivityListener

services:
    acme_user.activity_listener:
        class: %acme_user.activity_listener.class%
        arguments: [@security.context, @fos_user.user_manager]
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }

And you're good to go!

Edit (without FOSUserBundle)

1 Add this to your User Entity

Same as Step 1 Above

2 Create Event Listener

<?php
namespace Acme\UserBundle\EventListener;

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

/**
 * Listener that updates the last activity of the authenticated user
 */
class ActivityListener
{
    protected $securityContext;
    protected $entityManager;

    public function __construct(SecurityContext $securityContext, EntityManager $entityManager)
    {
        $this->securityContext = $securityContext;
        $this->entityManager = $entityManager;
    }

    /**
    * Update the user "lastActivity" on each request
    * @param FilterControllerEvent $event
    */
    public function onCoreController(FilterControllerEvent $event)
    {
        // Check that the current request is a "MASTER_REQUEST"
        // Ignore any sub-request
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        // Check token authentication availability
        if ($this->securityContext->getToken()) {
            $user = $this->securityContext->getToken()->getUser();

            if ( ($user instanceof User) && !($user->isActiveNow()) ) {
                $user->setLastActivityAt(new \DateTime());
                $this->entityManager->flush($user);
            }
        }
    }
}

3 Declare event Listener as a service

parameters:
    acme_user.activity_listener.class: Acme\UserBundle\EventListener\ActivityListener

services:
    acme_user.activity_listener:
        class: %acme_user.activity_listener.class%
        arguments: [@security.context, @doctrine.orm.entity_manager]
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }

And you're good to go!

Community
  • 1
  • 1
Mick
  • 30,759
  • 16
  • 111
  • 130
  • I am still struggling on how to create event listeners? Can you give me an example code how would I listen to the kernel.controller event and update the lastActivity? Should I create a service? – StarJedi Jan 29 '14 at 00:36
  • Thanks @Patt I have updated my question with the code. Please have a look – StarJedi Jan 29 '14 at 16:19
  • 1
    See my update, I tested myself and it works. Hope that helps! – Mick Feb 17 '14 at 04:49
  • Thanks for the update. Is there a way to implement it without using the FOS UserBundle? I tried the Entity manager injection in listener service. I passed @doctrine.orm.entity_manager as an argument but it gives an error like this - Catchable Fatal Error: Argument 2 passed to Acme\SecurityBundle\EventListener\ActivityListener::__construct() must be an instance of Doctrine\ORM\EntityManager – StarJedi Feb 24 '14 at 09:40
  • See my edit @CloudJedi, this is because the service for entity manager is `@doctrine.orm.entity_manager` and not `@doctrine`! That's a mistake in the [blog](http://www.symfony-grenoble.fr/en/238/list-online-users/) linked to that answer. Enjoy! – Mick Feb 24 '14 at 13:09
  • @CloudJedi, I am happy to help but the idea of is that we suggest code and then you tell us what works and what doesn't. How can I help if you don't tell me what error message you get. – Mick Feb 24 '14 at 16:02
  • Thanks for the reply. I should have elloborated further. The error message is ` ContextErrorException: Catchable Fatal Error: Argument 2 passed to Acme\SecurityBundle\EventListener\ActivityListener::__construct() must be an instance of Doctrine\ORM\EntityManager, instance of Doctrine\Bundle\DoctrineBundle\Registry given, called in C:\xampp\htdocs\sinfoc\public\app\cache\dev\appDevDebugProjectContainer.php on line 364 and defined in C:\xampp\htdocs\sinfoc\public\src\Acme\SecurityBundle\EventListener\ActivityListener.php line 2 ` – StarJedi Feb 24 '14 at 16:33
  • 3
    Since Symfony2.4 you could also use `if($event->isMasterRequest()){...}` to check if the current request is a `MASTER_REQUEST`. [link](http://symfony.com/doc/current/components/http_kernel/introduction.html#sub-requests) – jpbaxxter Sep 16 '14 at 13:16
  • Add a property "lastLogout" to the user, create a logoutlistener and save date on logout and in `isActiveNow()` you return false if there was a logout after lastActivity, so you do not see users as logged in if they logged out – john Smith Sep 19 '16 at 11:50
10

As I can't comment on posts, I'd still like to give a remark on the answer by Mick via this answer.

Since Symfony 2.6 the SecurityContext class is deprecated and, in this case, the TokenStorage class should be used instead.

Thus, the services.yml would be as follows:

services:
    acme_user.activity_listener:
        class: %acme_user.activity_listener.class%
        arguments: ['@security.token_storage', '@doctrine.orm.entity_manager']
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }

And, instead of

use Symfony\Component\Security\Core\SecurityContext;

One should

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;

(also replace the SecurityContext inside the class with the TokenStorage class)

Then, on line 38, the token availability would be checked using

$this->tokenStorage->getToken()

And, on line 39, the user instance would be obtained using

$this->tokenStorage->getToken()->getUser()
Eiko
  • 25,601
  • 15
  • 56
  • 71
Timo
  • 136
  • 1
  • 7
  • I know this is old post, but want to just correct your typo. In services.yml in arguments it should security.token_storage instead of security.context. – herr Feb 16 '16 at 09:09
4

In Symfony 4 I solved the problem in the following way.

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;

class ActivitySubscriber implements EventSubscriberInterface {

    private $em;
    private $security;

    public function __construct(
    EntityManagerInterface $em, Security $security) {
        $this->em = $em;
        $this->security = $security;
    }

    public function onTerminate() {
        $user = $this->security->getUser();

        if (!$user->isActiveNow()) {
            $user->setLastActivityAt(new \DateTime());
            $this->em->persist($user);
            $this->em->flush($user);
        }
    }

    public static function getSubscribedEvents() {
        return [
            // must be registered before (i.e. with a higher priority than) the default Locale listener
            KernelEvents::TERMINATE => [['onTerminate', 20]],
        ];
    }

}
Tomasz
  • 4,847
  • 2
  • 32
  • 41
1

For Symfony3.4 (4), I used EntityManagerInterface to update user, and Security to get user, following codes worked for me :

app/config/services.yml

AppBundle\Service\ActivityListener:
    tags:
        - { name: 'kernel.event_listener', event: 'kernel.controller', method: onCoreController }

Service/ActivityListener.php

<?php
namespace AppBundle\Service;

use AppBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Security\Core\Security;

class ActivityListener
{
    private $em;
    private $security;

  public function __construct(EntityManagerInterface $em, Security $security)
  {
    $this->em = $em;
    $this->security = $security;
  }

  public function onCoreController(FilterControllerEvent $event)
  {
    // Check that the current request is a "MASTER_REQUEST"
    // Ignore any sub-request
    if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
        return;
    }

    // Check token authentication availability
    if ($this->security->getToken()) {
        $user = $this->security->getToken()->getUser();

        if ( ($user instanceof User) && !($user->isActiveNow()) ) {
            $user->setLastActivityAt(new \DateTime());
            $this->em->persist($user);
            $this->em->flush($user);
        }
    }
  }
}
Chopchop
  • 2,899
  • 18
  • 36
RedaMakhchan
  • 482
  • 3
  • 9
0

Update for Symfony 3.4

1. Add this to your User Entity

Same as Step 1 Above

2. Create Event Listener

<?php
namespace Acme\UserBundle\EventListener;

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

/**
 * Listener that updates the last activity of the authenticated user
 */
class ActivityListener
{
    protected $tokenContext;
    protected $doctrine;

    public function __construct(TokenyContext $tokenContext, $doctrine)
    {
        $this->tokenContext= $tokenContext;
        $this->doctrine= $doctrine;
    }

    /**
    * Update the user "lastActivity" on each request
    * @param FilterControllerEvent $event
    */
    public function onCoreController(FilterControllerEvent $event)
    {
        // Check that the current request is a "MASTER_REQUEST"
        // Ignore any sub-request
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        // Check token authentication availability
        if ($this->tokenContext->getToken()) {
            $user = $this->tokenContext->getToken()->getUser();

            if ( ($user instanceof User) && !($user->isActiveNow()) ) {
                $user->setLastActivityAt(new \DateTime());
                $this->doctrine->getManager()->flush($user);
            }
        }
    }
}

3. Declare event Listener as a service

parameters:
    acme_user.activity_listener.class: Acme\UserBundle\EventListener\ActivityListener

services:
    acme_user.activity_listener:
        class: %acme_user.activity_listener.class%
        arguments: ['@security.token_storage', '@doctrine']
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }
unbreak
  • 1,000
  • 1
  • 16
  • 32