El problema

Cuando desarrollamos una aplicación web muchas veces descuidamos la optimización de los recursos con los que contamos. Olvidamos que en cada petición se realizan complejas consultas a nuestra base de datos, se realizan largos procesos, se cargan decenas de clases y se renderizan nuestras vistas. Todo ello, desde ya, es fundamental para el funcionamiento de nuestro sitio web. Sin embargo, gran parte de estos procesos se repiten innecesariamente a lo largo del día en cada petición realizada al servidor. En los "Términos y Condiciones", ¿qué diferencia podía haber entre las 10 AM y 10 PM en el renderizado? Incluso, en un blog donde escribimos una vez al día, ¿qué cambios imaginas que encontraremos entre la mañana y la noche? Si 1000 usuarios han ingresado a una página de nuestro sitio, 999 peticiones habrán despilfarrado recursos.

La solución

La mejor forma de solucionar nuestro problema es mediante un filtro de las peticiones y el uso de algún servicio de caché. En pocas palabras, cuando un usuario solicite acceder a una página de nuestro sitio, la aplicación deberá evaluar si la petición ya ha sido realizada recientemente. En caso afirmativo, deberá devolver el HTML guardado en caché. De lo contrario, la aplicación proseguirá normalmente hasta devolver una respuesta al cliente; en este caso, guardará temporalmente la respuesta en caché para futuras peticiones. ¿Fácil, no?

Cache HTTP en Laravel

En Laravel 5 disponemos de todas las herramientas necesarias para que este sistema de Cache HTTP, como lo llamaremos, sea puesto en funcionamiento en cuestión de minutos. Si aun no conoces el servicio de Cache de Laravel, te recomiendo que te interiorices con él. A modo de resumen, podemos decir que este servicio nos permite, de una forma fácil y rápida, guardar bajo una clave única un conjunto de datos para luego recuperarlos cuando haga falta. Asimismo, Laravel nos permite ejecutar un conjunto de acciones antes o después del controlador (middlewares). Requeriremos entonces, crear un middleware e implementar el servicio de Caché en esta guía.

Probablemente sepas qué es un middleware y cómo se usa. Para aquello que no lo sepan, comenzaremos dedicandole unas palabras. Como adelantamos, un middleware se ejecuta antes del controlador como una forma de filtrar las peticiones HTTP. En el middleware nosotros podemos realizar lo que querramos antes o después de la ejecución del resto de la aplicación. Por ejemplo, podemos verificar que el usuario está autenticado (y detener la aplicación) antes de que el Request pase al controlador. O también, si lo deseamos, podemos ejecutar otros procesos luego de que la respuesta para el cliente haya sido preparada. Es lo que en versiones anteriores de Laravel se llamaban filtros Before y After.

Los middlewares pueden ejecutarse, o bien para toda petición, o bien solamente para aquellas que determinemos en nuestro archivo de rutas. Tanto los primeros como los segundos deben ser registrados en las propiedades de App\Http\Kernel. Si queremos destinar un uso global al middleware, lo registraremos en la propiedad $middleware. Por el contrario, para un uso específico en el archivo de rutas, lo registraremos en la propiedad $routeMiddleware.

Como nuestro middleware no debe ser aplicado a todas las peticiones, sino sólo a las que nosotros querramos, deberemos registrarla bajo $routeMiddleware:

class Kernel extends HttpKernel
{
   //etc

   protected $routeMiddleware = [
        //all your route middlewares, as 'auth', etc.
        'httpCache' => \App\Http\Middleware\HttpCache::class,
   ];
}

La clave 'httpCache' será utilizada en las rutas de nuestra aplicación en las que querramos que el middleware HttpCache sea ejecutado. Por ejemplo:

// app\Http\routes.php

Route::group(['middleware' => 'httpCache:60'], function() {

// All your routes wrapped here

}

Como puedes ver, la clave 'httpCache' está seguida de '60' como parámetro a pasar a nuestro middleware. Queremos que el conjunto de rutas agrupadas tengan una duración en caché de 60 minutos.

Nos está faltando, por último, crear nuestro middleware. Para ello, crearemos un archivo 'HttpCache.php' dentro de la carpeta app\Http\Middleware :

<?php

namespace App\Http\Middleware;

use Closure;
use Cache;

class HttpCache
{
}

Dentro de nuestra clase, deberemos añadir el método handle encargado de manejar el request entrante. El primer argumento es el request, el segundo es una función cuyo llamado significa la ejecución del resto de la aplicación (incluidos todos los otros middlewares que debieran ejecutarse):

public function handle($request, Closure $next)
{
   // Código a ejecutar antes de que la aplicación continúe con el request

   $response = $next($request);

   // Código a ejecutar después de que la aplicación haya continuado con el request

   return $response;

}

Agregaremos un tercer parámetro correspondiente a los minutos. Veamos cómo queda nuestro middleware:

<?php

namespace App\Http\Middleware;

use Closure;
use Cache;

class HttpCache
{
   public function handle($request, Closure $next, $time = 60)
   {
      // Obtenemos la url de la petición y generamos con ella una
      // clave única para guardar en cache.

      $url = $request->fullUrl();
      $key = md5($url);

      // Si la clave existe en caché, hay que obtener el contenido,
      // devolverlo como respuesta y detener el resto de la aplicación.

      if(Cache::has($key)){
           $content = Cache::get($key);
           return response($content);
      }

      // De lo contrario, se continua con el resto de la aplicación...

      $response = $next($request);

      // ...y se guarda el contenido de la respuesta durante el tiempo
      // solicitado en caché bajo la clave única generada más arriba. 

      Cache::put($key, $response->getContent(), (float)$time);

      return $response;

   }
}

El haber incluido un parámetro adicional al middleware nos permite definir rutas con diferente tiempo de expiración. Por ejemplo, hay algunas que querremos que expiren a los 10 minutos y otras a las 24 horas. Por último, hay que agregar que si quisiéramos eliminar de caché una página, deberemos borrar el contenido asociado a la clave desde cualquier parte de nuestra aplicación:


$url = \Request::fullUrl();
$key = md5($url);
Cache::forget($key);