Cómo implementar una gateway cache basada en el sistema de archivos en Symfony 5

Todo lo que no necesite actualizarse constantemente, como el contenido de un artículo que se solicita muchas veces, pero que normalmente no se modifica, debe almacenarse en caché. Los beneficios de almacenar datos en caché en tu aplicación son los siguientes:

  1. Reduce las llamadas de red, especialmente cuando tu base de datos está alojada en otro servidor y no en el mismo servidor donde se está ejecutando el backend de la aplicación.
  2. Evita los recálculos.
  3. Reduce la carga de la base de datos (imagina que varios servidores solicitan la misma información una y otra vez desde la misma base de datos del servidor, extremadamente tedioso).

En este artículo, te explicaré cómo crear fácilmente tu propio grupo de caché usando un adaptador de caché del sistema de archivos en tu proyecto Symfony 5.

Nota: como menciona la documentación de Symfony, la sobrecarga del sistema de archivos IO a menudo hace que este adaptador sea una de las opciones más lentas. Si el rendimiento es primordial, se recomiendan los adaptadores en memoria (Apcu, Memcached y Redis) o los adaptadores de base de datos (Doctrine y PDO).

Sin embargo, esto no significa que no se pueda usar, ya que habrá casos en los que siempre será mejor almacenar en caché localmente que solicitar información de forma remota varias veces.

1. Instale el componente de caché de Symfony

Antes de continuar con la implementación, debe instalar el componente Symfony Cache. El componente Caché proporciona funciones que cubren necesidades de almacenamiento en caché desde simples hasta avanzadas. Implementa de forma nativa PSR-6 y los contratos de caché para lograr la mayor interoperabilidad. Está diseñado para el rendimiento y la resistencia, se envía con adaptadores listos para usar para los backends de almacenamiento en caché más comunes.

Puedes instalarlo con composer con el siguiente comando:

composer require symfony/cache

Puedes visitar la documentación oficial en el sitio web de Symfony aquí , la página de packagist o la página del repositorio de Github aquí.

2. Crear CacheInterface

Como primer paso, crea una interfaz vacía con el nombre CacheInterface en el directorio src/Utils de tu proyecto Symfony. La interfaz tiene este aspecto:

<?php
// app/src/Utils\Interfaces\CacheInterface.php

namespace App\Utils\Interfaces;

interface CacheInterface {}

Siguiendo este enfoque, podrás cambiar dinámicamente desde el adaptador de caché (creando un grupo de caché) sin comprometer todo tu código, ya que deberían funcionar con el mismo patrón. Cambiar el mecanismo de caché subyacente de este a Redis o al caché basado en bases de datos no será un problema. Verás cómo esto es posible en el siguiente paso.

3. Crear clase de caché del sistema de archivos

Necesitamos crear una nueva clase PHP en el directorio de utils que implementará la CacheInterface creada previamente. Dentro del constructor, crearemos una instancia de la clase FilesystemTagAwareAdapter del componente de caché de Symfony para usar una invalidación basada en etiquetas, ya que esta clase ofrece un mejor rendimiento que envolver la clase FilesystemAdapter dentro de la clase TagAwareAdapter:

<?php

// app/src/Utils/FilesCache.php
namespace App\Utils;

use App\Utils\Interfaces\CacheInterface;
use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter;

class FilesCache implements CacheInterface 
{    
    /* @var $cache FilesystemTagAwareAdapter */
    public $cache;
    
    public function __construct(string $projectDir, string $env) {
        $this->cache = new FilesystemTagAwareAdapter(
            // una cadena utilizada como subdirectorio del directorio raíz de la caché, donde los items de la cache
            // serán almacenados
            'FilesystemCache',
             // la vida útil predeterminada (en segundos) para los elementos de la caché que no definen su
             // propia vida útil, con un valor 0 que hace que los elementos se almacenen indefinidamente (es decir,
             // hasta que se eliminen los archivos)
            $TTL = 3600,
             // el directorio de caché principal (la aplicación necesita permisos de lectura y escritura)
             // si no se especifica ninguno, se crea un directorio dentro del directorio temporal del sistema
            $projectDir . DIRECTORY_SEPARATOR . "var/cache/$env"
        );
    }
}

4. Registrar CacheInterface

Asegúrate de registrar CacheInterface, proporcionando la clase creada en el paso 2, ya que se utilizará como adaptador:

# app/config/services.yaml
services:
    # Registrar la CacheInterface
    App\Utils\Interfaces\CacheInterface:
        # Puede cambiar el adaptador de caché con una nueva clase aquí
        class: 'App\Utils\FilesCache'
        # Pasar como argumentos los parámetros requeridos del directorio raíz
        # del proyecto Sf y el entorno actual
        arguments:
            $projectDir: '%kernel.project_dir%'
            $env: '%kernel.environment%'

5. Ejemplo de cómo utilizar CacheInterface

Puede almacenar en caché todo lo que se pueda almacenar en archivos gracias al enfoque clave-valor de todos los grupos de caché. En este ejemplo, le mostraremos cómo almacenar en caché una respuesta completa (todo el HTML enviado al navegador) de la ruta blog_show de un controlador que espera la identificación y el slug de un artículo (que se utilizará como clave de el elemento en caché):

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

// Una entidad de ejemplo
use App\Entity\MyExampleEntity;

// Un repositorio de ejemplo
use App\Repository\MyExampleEntityRepository;

// Importar CacheInterface
use App\Utils\Interfaces\CacheInterface;

class BlogController extends AbstractController
{
     /**
     * Muestra la vista de presentación de MyExampleEntity.
     *
     * @Route("/blog/{id}/{slug}", name="blog_show")
     */
    public function show(
        int $id, 
        string $slug,
        MyExampleEntityRepository $repo,
        CacheInterface $cache
    ): Response
    {
        /* @var $cache \Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter */
        $cache = $cache->cache;
        
        // Esta será 1 consulta
        /* @var $entity MyExampleEntity */
        $entity = $repo->find($id);
         
        // Asigne un elemento de caché con una clave personalizada, en este caso usaremos la ruta y los parámetros para crear un identificador único
        $cachedItem = $cache->getItem("blog_show" . $id . $slug);

        // Caduca a los 30 minutos (1800 segundos)
        $cachedItem->expiresAfter(1800);
        
        // Si no hay ningún recurso para este elemento en la caché, créelo
        if(!$cachedItem->isHit())
        {
            // Prepara la respuesta tradicional en el controlador
            // Imagina que el repositorio ejecutará 4 consultas
            $response = $this->render('blog/show.html.twig', [
                'entity' => $entity,
                'relatedBlogs' => $repo->findRelatedBlogs($entity),
                'latestBlogs' => $repo->findLatestBlogs()
            ]);
            
            // Almacene la respuesta completa en el elemento de caché
            $cachedItem->set($response);

            // Y persistir en el caché
            $cache->save($cachedItem);
        }
        
        // Devuelve el elemento almacenado en caché, en este caso la respuesta
        // Entonces, si la respuesta se almacenó en caché, guardó 4 consultas de sobrecarga
        return $cachedItem->get();
    }
}

Cuando no deberías usar este enfoque de almacenar en caché toda la página como lo hice en el controlador

Como dije anteriormente, este es solo un ejemplo de lo que puede almacenar en caché usando este adaptador de sistema de archivos. No debes usar el código del controlador si:

  • Una parte de la página es dinámica, por ejemplo, un teletipo de noticias o una sección que enumera los últimos artículos publicados. O si tienes un panel de control personalizado que solo le aparece al administrador de la página. Como si la página estuviera en caché cuando el administrador inició sesión, el HTML del panel de control también se almacenará en caché para todos los que accedan a esa página.
  • Imagine que un visitante agrega un producto al carrito de compras de su cuenta y la ruta es almacenada en caché por primera vez por este usuario (Carlos). Cuando otras personas inician sesión en la aplicación y visitan la misma página, los mismos elementos que Carlos agregó al carrito de compras aparecerán en el carrito de compras del nuevo usuario, solo porque toda la página se ha almacenado en caché y se está sirviendo en el mencionado URL. Esa es la desventaja de almacenar en caché todo el HTML.

Siempre debes tener cuidado con lo que almacena en caché, ya que a veces no debe almacenarse en caché.

5. Borrado de caché

Como la implementación de la clase FilesCache almacena la información en caché dentro del directorio symfony-project/var/{env}/, borrar la caché en el entorno en el que está trabajando actualmente hará el trabajo:

php bin/console cache:clear

Que te diviertas ❤️!

Esto podria interesarte

Conviertete en un programador más sociable