Tuesday, February 28, 2017

How to list all logged-in users in Laravel and Redis?

I'm currently making an app on Laravel (5.3) and require a list of all logged-in users. I was originally going to store them in MySQL but was advised that Redis would be better for the job. Once I looked at Redis docs I was going to store all the users in a set, but then realised you can't set an expire time on individual members, and so opted for namespaced strings instead.

I have written some code that I believe is functioning correctly, but would like advice on improving it/fixing any problems there might be.

So first, here are the two functions I added in LoginController.php

// Overriding the authenticated method from  Illuminate\Foundation\Auth\AuthenticatesUsers
protected function authenticated(Request $request, $user)
{
    $id = $user->id;

    // Getting the expiration from the session config file. Converting to seconds
    $expire = config('session.lifetime') * 60;

    // Setting redis using id as namespace and value
    Redis::SET('users:'.$id,$id);
    Redis::EXPIRE('users:'.$id,$expire);
}

//Overriding the logout method from Illuminate\Foundation\Auth\AuthenticatesUsers
public function logout(Request $request)
{
    // Deleting user from redis database when they log out
    $id = Auth::user()->id;
    Redis::DEL('users:'.$id);

    $this->guard()->logout();

    $request->session()->flush();

    $request->session()->regenerate();

    return redirect('/');
}

Next I wrote Middleware called 'RefreshRedis' in order to refresh the expiration on the Redis when the user does something that refreshes their session.

public function handle($request, Closure $next)
{
    //refreshing the expiration of users key
    if(Auth::check()){
        $id = Auth::user()->id;
        $expire = config('session.lifetime') * 60;
        Redis::EXPIRE('users:'.$id,$expire);
    }
    return $next($request);
}

I then registered the Middleware in $middlewareGroups just after the StartSession Middleware

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \App\Http\Middleware\RefreshRedis::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];

In order to get a list of all the users I used a modified version of the function found in this thread.

class Team extends AbstractWidget
{

/**
 * Treat this method as a controller action.
 * Return view() or other content to display.
 */
public function run()
{
    //Find all logged users id's from redis
    $users = $this->loggedUsers('users:*');

    return view('widgets.team',compact('users'));
}


protected function loggedUsers($pattern, $cursor=null, $allResults=array())
{
    // Zero means full iteration
    if ($cursor==="0"){
        $users = array ();

        foreach($allResults as $result){
            $users[] = User::where('id',Redis::Get($result))->first();
        }
        return $users;
    }

    // No $cursor means init
    if ($cursor===null){
        $cursor = "0";
    }

    // The call
    $result = Redis::scan($cursor, 'match', $pattern);

    // Append results to array
    $allResults = array_merge($allResults, $result[1]);

    // Get rid of duplicated values
    $allResults = array_unique($allResults);

    // Recursive call until cursor is 0
    return $this->loggedUsers($pattern, $result[0], $allResults);
}
}




via Dalek

Advertisement