Erik Belusic

General

Tracking If a User Is Currently Online in Laravel (the easy way)

Posted on . 5 minute read

Tracking If a User Is Currently Online in Laravel (the easy way)

Introduction

Today I was tasked with adding a little green dot next to a username, denoting if they are online or not, on a user profile page, in a Laravel app. My first thought was that we were going to need to spin up a node.js server and track of active socket connections for each user. Then with the currently logged in users socket, we can update online status in real time! The only issue is that this was a little bit over the top for our current requirement, and was not completely necessary until we have features that require up to the second accuracy such as real-time chat.

A colleague  pointed out that for the current need, the way that MySpace used to handle the “online” feature may be sufficient. To the best of our knowledge, the way that MySpace used to display if a user was online or not was based on their last activity on the site. If their last activity was within X minutes, we display the “online” badge, and if not, we don’t. Simple!

Let’s just add a column to the users table for that user’s last activity, and update that on every page request. Then when we need to check if the user is online, we can compare that timestamp with the current timestamp, and if it is within X minutes, they are online! While that would work just fine, depending on the application you are building, it adds unnecessary “writes” to the database, which could at some point slow down your application as you scale. A great compromise would be to store this information in the application cache. The great thing about the cache is that it simplifies this method because the cache can be set to expire.

Now that we decided we are going to implement this feature using the cache, the next question is where should this code go so that it runs on every request? I had two thoughts on where this should go:

  1. the constructor to the BaseController, that all of your application controllers extend
  2. a middleware

After some thought, and realizing that I would need to go and add calls to the parent constructor in all my already written constructors, I opted to try this out in a middleware.

We have a plan, so let’s get into the code!

First is to create the middleware, so from the terminal let’s run

php artisan make:middleware LogLastUserActivity

Now let’s open up that file located at app/Http/Middleware/LogLastUserActivity.php. Inside the handle method, we need to add the following code:


if(Auth::check()) {
    $expiresAt = Carbon::now()->addMinutes(5);
    Cache::put('user-is-online-' . Auth::user()->id, true, $expiresAt);
}

Next, let’s jump over to app/Http/Kernel.php. If using Laravel 5.0.* or 5.1.* or earlier, you can place this in the main $middleware array, and if you are using 5.2.*, you can place this in the middleware group for web. It is important that it’s added after the StartSession middleware, otherwise, the Auth facade will not have access to the logged in user. My updated array was as follows:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class
        \App\Http\Middleware\VerifyCsrfToken::class,
        \App\Http\Middleware\LogLastUserActivity::class,
    ],
    'api' => [
        'throttle:60,1',
    ],
];

The last step is to add a method to our user object to check this value. In app/User.php we add the following method:

public function isOnline()
{
    return Cache::has('user-is-online-' . $this->id);
}

Now in any of your views you can use the following:


@if($user->isOnline())
    user is online!!
@endif

IMPORTANT NOTE – make sure to import all facades with use statements at the top of your files!

I hope this helps someone out!

Erik Belusic

Erik Belusic

https://erikbelusic.com/

I'm Erik. I am currently a Web Developer at SYPartners. I think I'm gonna give this blogging thing a shot!

Comments
  • user

    AUTHOR Nick

    Posted on 11:12 am February 24, 2016.
    Reply

    Nice makes sense! A very simple and clear way to do it thanks Erik!

    • user

      AUTHOR Nasir

      Posted on 7:30 am October 15, 2016.
      Reply

      i got an error $user is undefined on view

  • user

    AUTHOR Matthew

    Posted on 7:42 am March 5, 2016.
    Reply

    Hi, Thank you for posing this but i have one last hurdle. I am pretty new to laravel 5.2. I get a variable undefined error in the view for : $user. I tried assining $user=DB::table(‘users’)->where(‘id’,1)->first(); but still get error. Could you help me out please. Thanks in advance

    • user

      AUTHOR Erik Belusic

      Posted on 12:09 pm March 5, 2016.
      Reply

      Hi, Matt.

      It would be helpful to see the full code for the controller method or closure that you are using to create the view, but it sounds like you aren’t passing the $user variable to your view correctly. The last line of your controller method should look something like:

      return view('your-view-name')->with('user', $user);
  • user

    AUTHOR John Lax

    Posted on 2:54 am May 26, 2016.
    Reply

    Hi Bro , I want to show how many people are viewing the website, please help me!

    • user

      AUTHOR Erik Belusic

      Posted on 12:08 pm June 4, 2016.
      Reply

      Hi John, if you have a site where users are logging in and have a use id, you may want to take a look at Matt’s solution down below. (https://erikbelusic.com/tracking-if-a-user-is-online-in-laravel/#comment-11). If you just want all visitors to be cached, you can take Matt’s solution and possibly use their IP as an identifier, however, that isn’t the problem I intended to solve with this solution. Showing presence of visitors or users on a page is something that would be more suited by a solution utilizing web sockets, such as socket.io + redis or pusher.

      • user

        AUTHOR Nasir

        Posted on 7:38 am October 15, 2016.
        Reply

        i got an nothing when i add an if statement

        $loggedUser = new User();
        if($loggedUser->isOnline()){
        dd(“i am online”);
        }
        else {
        dd(‘nope’);
        }
        it came with else all the time. My main aim is to count the logged in users in my website. Thanks for your time

  • user

    AUTHOR nandar

    Posted on 7:18 am May 26, 2016.
    Reply

    In my localhost,these code doesn’t work. 🙁

  • user

    AUTHOR nandar

    Posted on 9:49 am May 26, 2016.
    Reply

    These code doesn’t work for me and Please help me with more detail code.
    Thanks

    • user

      AUTHOR Erik Belusic

      Posted on 12:09 pm June 4, 2016.
      Reply

      Hi Nandar,

      If you want to post your code to a gist and link me to it, or link me to a repo, I’d be happy to take a look.

  • user

    AUTHOR Mark

    Posted on 3:49 pm May 26, 2016.
    Reply

    Great tutorial! It’s much simpler than some solutions out there. I ended up using it and even extending this method to also provide a global count of all the online users.

    In my case I wanted to have a ‘users online’ count in the header of my site (because I’m creating a web based game), so I decided I needed to utilize a custom Service Provider so that my variable would be accessible to every view that included my header.blade.php file.

    So I ran php artisan make:provider OnlineUsersProvider to create my custom Service Provider.

    Next, I registered the service provider in the “providers“ array of config/app like so: “App\Providers\OnlineUsersProvider::class,“ .

    In my “OnlineUsersProvider“ file I then added this line of code in the “boot()“ method:

    $this->getOnlineUsersCount();

    Then I actually create the “getOnlineUsersCount()“ method underneath the “register()“ method like so:

    private function getOnlineUsersCount()
        {
            view()->composer('layout.partials.header', function($view)
            {
                $users = Users::all()->where('is_active', 1); // To help server load, I only target users that are active. In my case my app changes this field for users that haven't logged in for over a week.
                
                $onlineUsersCount = 0;
                
                foreach ( $users as $user )
                {
                    if ( Cache::has('user-is-online-' . $user->id) )
                    {
                        $onlineUsersCount++;
                    }
                }       
                    
                $view->with([ 'onlineUsersCount' => $onlineUsersCount ]);
            });
            
        }

    Finally, in my view I echoed out the counted online users: “{{ $onlineUsersCount }}“

    Done. Now you can see all users who are online (within the last 5 minutes). You could further optimize this to have a single cache file containing the total count and check to see if it exists, if not, loop through the users and recreate it. That way you don’t have to loop over all your users on every request.

    • user

      AUTHOR Erik Belusic

      Posted on 12:20 pm June 4, 2016.
      Reply

      Hey Matt,

      I’m glad you liked my approach. Thats a pretty clever solution! The first optimization I would make if I were you is to cache

      $users = Users::all()->where(‘is_active’, 1);

      for 24 hours, so that you aren’t running that DB query on every request. IMO that would be a better optimization than trying to avoid the foreach loop. Depending on the number of users you have, the DB query is going to be a more expensive task than the foreach loop, especially if you are running a php7 server.

      I’m not certain if this is the correct syntax anymore, but I’m sure you can find it with some digging. It would look something like

      $users = Users::where(‘is_active’, 1)->remember(1440)->get();

      If you update the is_active daily, there is no need to run this query more than once per day.

    • user

      AUTHOR Ciredi

      Posted on 3:44 pm July 16, 2016.
      Reply

      Cleaning up the foreach with collections 🙂

      $onlineUsersCount = collect($users)->reduce(function ($count, $user) {
      if ( Cache::has('user-is-online-' . $user->id) ) {
      $count++;
      }
      return $count;
      }, 0);

      • user

        AUTHOR Erik Belusic

        Posted on 7:43 pm September 7, 2016.
        Reply

        Hey Mark,

        I too have read Adam’s book =]. Sorry for the EXTREMELY delayed response. I’ve been quite caught up in a new job!

  • user

    AUTHOR آموزش php

    Posted on 12:59 pm September 11, 2016.
    Reply

    hi Erik
    It was an interesting idea.
    Thanks

  • user

    AUTHOR Emeka

    Posted on 1:52 pm October 6, 2016.
    Reply

    Very simple and straight forward.
    For people using memcached its a great idea to use Cache Tags.

    Cache::tags(['users', 'online'])->put(Auth::user()->username, Auth::user()->id, $ExpiresAt);

  • user

    AUTHOR Raj Patel

    Posted on 6:03 am October 7, 2016.
    Reply

    Hello Erik, thanks for the good explanation, i would like to ask few questions regarding the same. Is it possible to track user’s location and language in Laravel ? Is it possible to find total number of online visitors on your website?

    • user

      AUTHOR Erik Belusic

      Posted on 8:02 pm November 4, 2016.
      Reply

      Yes, while out of the scope of this article, you should be able to use a GEOIP library for location. As far as language goes, you would want to check the Accept-Language header in the request – see this stack thread – https://stackoverflow.com/questions/6157485/content-language-and-accept-language/6157546#6157546

      For total online visitors utilizing my method of tracking, there is no effecient way, but you could fake it. When caching the user online key, you could set a total that expires every couple of minutes and then check for actual online users by looping over all users and checking if they have a key. # of users online would be better served by a more robust solution with sockets and actually knowing the total number of users online.

  • user

    AUTHOR Phillipe

    Posted on 7:51 pm October 17, 2016.
    Reply

    Please edit the middleware so it includes the complete handle method because I just lost half an hour because I deleted the return $next($request); response. It is not smart of me but I assume I’m not the only one. Otherwise, nice tutorial, thanks!

  • user

    AUTHOR KnightsOfNi

    Posted on 3:09 am November 3, 2016.
    Reply

    Hey, just letting you know this clown stole your article:
    edited: removed link

    • user

      AUTHOR Erik Belusic

      Posted on 7:53 pm November 4, 2016.
      Reply

      Thanks for looking out! I see he credited it to me… but still totally not cool. I’m reaching out to some other authors to let them know. Thanks again!

  • Markdown is supported

    View Comments (21) ...
    Navigation