In this tutorial, we're going to learn how you can create a caching layer in your Laravel application to help speed up your website and reduce the number of requests to your database. We're also going to set up automatic functionality which will flush your cache on specific models when changes happen using Eloquent events.
Caching In Laravel
Laravel provides a very easy to use caching functionality to store and get values from multiple different types of data storage. You can locate the caching configuration at config/cache.php
, in this file you'll define what type of caching driver you want to use. Laravel allows you to use multiple types such as file driven, database driven and even memcached or redis. In this tutorial, we're not going to go into too much detail on the usage of Laravel caching as you can read about how to use caching on the Laravel documentation. Laravel Caching
Types of Cache
As said above there are multiple types of caching drivers you can use in Laravel, the quickest drivers you can use are the popular Memcached or Redis. I would recommend you use one of these in your next project. If you're using Homestead for your development environment you will already have Memcached and Redis installed and ready to use.
Eloquent Events
The Eloquent models will fire multiple events during the times it will interact with your database. The event lifecycle is: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored. These events allow us to easily attach code and specific lifecycle events to change the scope of the application. In this tutorial, we're going to use the saved
event to clear the cache for a specific model so the next GET will retrieve any new changes.
What We're Going To Achieve
The purpose of this tutorial we're going to attach events to specific models which will fire a new event attached to a model to clear cache for this model. We're going to use Cache tags to group multiple caches by the model so that we can easily flush this group when data has been updated. We're also going to use a repository layer to interact with the database so that attach the caching inside the repository layer to ensure all database requests will attempt to first retrieve from the cache first. If you haven't used the repository design pattern before or don't understand the benefits of it, send me an email or tweet me and I'll be happy to go into more detail for you.
Create Cache Service Provider
First, we need to create the cache service provider which we will use to attach the Eloquent events to specific models. In Laravel, it's very easy to create a new service provider by using the below artisan command.
php artisan make:provider CacheServiceProvider
This will create a new file at App/Providers/CacheServiceProvider.php
. Within this file, you'll see the class is already created for you with a boot function, this is where we need to attach to the model. To attach an event to the model you need to use the static methods saved
and deleted
.
Model::saved(function(){
// fire event
});
Model::deleted(function(){
// fire event
});
As we only want this event to be fired on specific models we're going to build up an array of models in a private property and loop through these to fire the event. Below is the full code we're going to use on the service provider.
namespace App\Providers;
use App\Events\ClearModelCache;
use Illuminate\Support\ServiceProvider;
class CacheServiceProvider extends ServiceProvider
{
// Add models to this array to loop through
private $cacheableModels = [
Model::class
];
/**
* Loop through all cacheable models and attach an event to clear cache
*/
public function boot()
{
foreach($this->cacheableModels as $cacheableModel)
{
$cacheableModel::saved(function($model) {
$this->fireEvent($model);
});
$cacheableModel::deleted(function($model) {
$this->fireEvent($model);
});
}
}
/**
* Fire the clear model cache event
*
* @param $model
*/
private function fireEvent( $model )
{
event( new ClearModelCache( $model ) );
}
}
To activate the new service provider we need to add it to providers list inside config/app.php
App\Providers\CacheServiceProvider::class,
Now this provider will run on each boot call.
Events & Listeners
As you saw in the above service provider we create a function called fireEvent
which runs the event ClearModelCache
we now need to create this in our application. You'll see in your Laravel application there is a file at app/Providers/EventServiceProvider.php
which has a protected property for listen, which is where you define all your events and the listeners that will run when this event is triggered. Add the following in the EventServiceProvider.php
file.
namespace App\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'App\Events\ClearModelCache' => [
'App\Listeners\ClearCache',
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}
We can make Laravel generate these file by running the command.
php artisan event:generate
If you look inside App/Events
you'll see our new ClearModelCache
Event has been created. The event classes are just data containers that can be accessed in the listeners, inside this event we need to inject in the Eloquent model so we can access the name of the table. You'll see we pass in the model in the constructor and store this in a private variable and create a public method to access this model property.
namespace App\Events;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Queue\SerializesModels;
class ClearModelCache
{
use SerializesModels;
/**
* @var Model
*/
private $model;
/**
* Create a new event instance.
*
* @param Model $model
*/
public function __construct( Model $model )
{
$this->model = $model;
}
/**
* @return Model
*/
public function getModel()
{
return $this->model;
}
}
With the event created we can now create the listener for this event, look inside the App/Listener
folder and you'll see a file called ClearCache
, the purpose of this file is to clear the cache with the tag of the model name. Inside the handle method, we will retrieve our event object, from this event object we can access the model using the getModel
method, from the model we can then call getTable
to get the table name. Pass in the table name into the Cache::tags()
method will get all the cache objects with this specific tag we're then able to delete all of these cache objects by using the flush()
. View the code below to see how this will work.
namespace App\Listeners;
use App\Events\ClearModelCache;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Cache;
class ClearCache implements ShouldQueue
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param ClearModelCache $event
* @return void
*/
public function handle(ClearModelCache $event)
{
Cache::tags($event->getModel()->getTable())->flush();
}
}
Database Repository Pattern
The repository pattern is needed in this use as it gives us a single point of access to our database, we need this single point of access so we can always check the cache before returning the data. Here's a good explanation of the repositry design pattern on tutsplus. In Laravel, they've made it very easy to get data out of the database, for example, if we want to display a all users in your controller you could just go.
return User::all();
The problem with this is that it will always access the users from the database and not the cache so we need to wrap this around the Cache facade.
return Cache::tags( 'users' )->remember('all-users', 60, function() {
return User::all();
});
This now checks the cache for all-users
and if it doesn't exist or has expired then it will fetch the users again and store them in the cache, therefore the next time it performs this action it will retrieve the values from cache. But if we put this in the controller we need to add this same caching logic, cache tags, cache name, cache timeout in all the other places of the application we want to get all users. This is why we use the repository pattern to have a single point to make database queries and store this caching and retrieving logic in the repository and just call the repository from the controller.
return $this->userRepository->getAll();
Now on every database query, we will always check the cache before accessing the database, therefore reducing the database requests and speeding up our application. Enjoy the speed!