Caching routes with Laravel 4

Caching routes with Laravel 4

Yesterday I blogged about how I organise my Laravel routes within my Laravel applications, this post is a follow on to more route goodness.

I was experimenting with a clients website, trying to speed the page load up on the server side. A neat trick that I came up with is to use the cache layer to store a cached version of a page and serve it from that rather than generating it again.

This technique works amazingly well for pages which don’t change very often:

  • Homepage (depending on what’s on there)
  • About Us
  • Product information
  • Contact us forms

Whilst I don’t have any statistics, you can see the improvements in action here . You may need to reload the page again to see it. I’ve set the cache up to last for one hour.

Getting Started

Let’s create a filters within the app folder. Here is where we’ll store our custom route filters. It’s a good idea to split up different parts of your projects. Remember SRP .

Once we have a directory we need to add it to our classmaps:

composer.json

"autoload": {
  "classmap": [
    "app/commands",
    "app/controllers",
    "app/models",
    "app/database/migrations",
    "app/database/seeds",
    "app/filters"
  ]
}

app/start/global.php

ClassLoader::addDirectories(array(  
  app_path().'/commands',
  app_path().'/controllers',
  app_path().'/models',
  app_path().'/database/seeds',
  app_path().'/filters',
));

Create the filter

Within the app/filters directory you now need to create a file calledCacheFilter.php with the following content:

<?php

  class CacheFilter {
    public function filter($route, $request, $response = null) {
      $cacheContentKey = 'request-' . Str::slug(Request::url());

      if (is_null($response) && Cache::has($cacheContentKey)) {
        return Cache::get($cacheContentKey);
      } elseif (!is_null($response) && !Cache::has($cacheContentKey)) {
        // I'll explain this in a minute...
        if (Config::get('app.debug')) {
          Cache::put($cacheContentKey, $response->getContent(), 60);
        } else {
          Cache::tags('routes')->put($cacheContentKey, $response->getContent(), 60);
        }
      }
    }
  }

Ok, so you may be wondering about the Config::get('app.debug') condition. Locally I tend to use file for the cache driver. Both the file and database drivers don’t support tags. Tags are an incredibly powerful feature in Laravel’s caching system. I use tags here so that the administration panel is able to clear out the caches if something changes. You could add more tags here on a “directory” level for example.

Register the filter

In our existing app/filters.php file we simply add:

Route::filter('cache', 'CacheFilter');  

The first parameter of the filter method is the name that we’ll call later on, it’s how we refer to the class and the second paramater is the classname of the filter we just created.

This line can go in any file that is auto-loaded. It’d also make sense to put it intoapp/routes.php . It doesn’t matter too much in this case.

Using the filter

Here’s the good bit. Our existing routes don’t change too much:

Previously we’d have:

// Homepage
Route::get('/', 'HomeController@showIndex');  
// Dates & Prices
Route::get('/dates', 'HomeController@showDates');  
// Accommodation
Route::get('/accommodation', 'HomeController@showAccommodation');  

Whereas we’d now add a line above and below the routes that we want to cache:

Route::group(['before' => 'cache', 'after' => 'cache'], function() {  
    Route::get('/', 'HomeController@showIndexPage');
    Route::get('/products', 'HomeController@showProductsPage');
    Route::get('/contact', 'HomeController@showContactPage');
});

Ok, but what’s happening?

If you’re not sure what’s going on here, let me break it down.

When the route is requested we run two functions:

  1. Before the page is loaded we call the cache filter that we registered. If the cache exists then it’ll return with that. Nothing else will happen. If it doesn’t exist, then we just return the original content.
  2. After the page has been returned we run the cache again. Now, what’s different here is that the $response variable will no longer be null . Infact it’ll be an object containing what we sent back to the user. We now store that content in the cache, which, when the next request to that page is made will be served instead.

I’ve seen massive improvements to it and I believe that you will too.

Pro tip!

For best results you should use Memcached for your cache as it’ll handle auto purging stale records. It’s very cool!

Comments are closed.