Paulund
2018-06-03 #laravel

Laravel Cache Auth::user()

If you use Laravel debug bar when developing your app you'll notice there is a tab that will show you all the database queries your application is making.

This is a great tool to spot areas where you can improve the database queries or if there's data you can cache instead of making another database call.

When working on a project I noticed that when logged in, the application was making at least one extra database query to fetch the logged in user.

These calls are commonly called by checks to either get the logged in user by using Auth::user() or to check if the user is logged in by using Auth::check().

These two calls will look up the user ID in session and then go an fetch the user record from the database based off this ID, therefore this is something we can change to cache the user record.

How Is This Going To Work?

For this to work we're going to create a new user Auth provider which will inherit from the default \Illuminate\Auth\EloquentUserProvider, this will override the retrieveById method and get the value from cache instead of calling the database.

This will also need to update the cache whenever the user record is changed, allowing us to update the value with the new version of the user record.

Cache User Provider

First we're going to create a new user provider which will inherit from the EloquentUserProvider this will attempt to get the user from cache, if no user is found then it will fetch the user from the database. To do this it will need to override the retrieveById method so that this is called from the auth guards.

<?php

namespace App\Auth;

use App\Models\User;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Support\Facades\Cache;

/**
 * Class CacheUserProvider
 * @package App\Auth
 */
class CacheUserProvider extends EloquentUserProvider
{
    /**
     * CacheUserProvider constructor.
     * @param HasherContract $hasher
     */
    public function __construct(HasherContract $hasher)
    {
        parent::__construct($hasher, User::class);
    }

    /**
     * @param mixed $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        return Cache::get("user.$identifier") ?? parent::retrieveById($identifier);
    }
}

Register The CacheUserProvider

With the CacheUserProvider created we can register this as a new provider by adding the following into the auth service provider class.

<?php

namespace App\Providers;

use App\Auth\CacheUserProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [

    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        // Caching user
        Auth::provider('cache-user', function() {
            return resolve(CacheUserProvider::class);
        });
    }
}

This allows us to to use the 'cache-user' provider to fetch users.

Using The cache-user Provider

With the CacheUserProvider registered as cache-user we can choose to use this as our user provider by changing the config/auth.php providers property.

// Config/auth.php
'providers' => [
        'users' => [
            'driver' => 'cache-user',
            'model' => App\Models\User::class,
        ]
    ]

When fetching users Laravel will use our new provider where we can lookup in cache first instead of fetching from the database.

User Observer

Laravel with now use our provider to fetch the user, but we need to make sure that once the user is retrieved we add this user to cache. We need to make sure that when the user is updated we replace the cache with the updated user. We also need to make sure that if the user is deleted we remove the user from cache.

For this functionality we're going to use an observer to run off eloquent events and perform different functionality when the events are triggered.

To create an observer, add a new folder to your app directory and add the following file.

<?php
namespace App\Observers;

use Illuminate\Foundation\Auth\User;
use Illuminate\Support\Facades\Cache;

/**
 * User observer
 */
class UserObserver
{
    /**
     * @param User $user
     */
    public function saved(User $user)
    {
        Cache::put("user.{$user->id}", $user, 60);
    }

    /**
     * @param User $user
     */
    public function deleted(User $user)
    {
        Cache::forget("user.{$user->id}");
    }

    /**
     * @param User $user
     */
    public function restored(User $user)
    {
        Cache::put("user.{$user->id}", $user, 60);
    }

    /**
     * @param User $user
     */
    public function retrieved(User $user)
    {
        Cache::add("user.{$user->id}", $user, 60);
    }
}

Attach The Observer To The User

With the observer created we need to attach this to the User model, this is done inside the AppServiceProvider.

<?php

namespace App\Providers;

use App\Models\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Now when you log into your application you'll notice that it will no longer be getting the user from the database and it will be able to be fetched from cache instead.