Tutoriel Vidéo PHP Russian Doll Caching

Télécharger la vidéo Télécharger les sources

Je vous propose aujourd'hui de découvrir une nouvelle méthode pour gérer votre cache j'ai nommé le Fragment Caching et le Russian Doll Caching (idée originale aperçue chez BaseCamp).
Le principal problème que l'on rencontre avec la mise en cache c'est qu'il est difficile de déterminer à quel moment ce cache doit être invalidé. Pour remédier à ce problème, on peut utiliser la date de dernière modification d'un enregistrement comme clé de cache. Ainsi lorsqu'un enregistrement est modifié, la clé va changer, et un nouveau cache sera alors généré. Avec cette méthodologie on va mettre en cache des fragments de notre page, mais on pourra aussi mettre en cache des éléments à l'intérieur de blocs déjà en cache. Ainsi lors du rafraîchissement d'un enregistrement seul une partie de la page et régénérée.

Le principe

Comme énoncé précédemment le principe est d'entourer une partie de notre code HTML afin de le mettre en cache et d'utiliser un enregistrement pour générer la clé. Par exemple si on a une liste d'articles à mettre en cache :

  • On va récupérer la date de dernière modification du dernier article et l'utiliser comme clé pour la mise en cache du listing d'articles.
  • On va aussi mettre en cache individuellement chacun des articles afin de pouvoir réutiliser ces blocs à différents endroits de notre site.

L'avantage de cette technique-là et qu'elle permet d'éviter de générer trop de requêtes SQL, mais aussi d'alléger le fonctionnement de notre code en limitant la génération de code HTML. Si un enregistrement est modifié, seul le code lié à cet enregistrement se trouvera régénéré.


<?php 
// Notre Fragment caching doit pouvoir être construit avec différents adapter (Redis, Memcached...)
$cache = new \Grafikart\Cache(new \Grafikart\RedisCache());
?>

<?php $cache->cache(['posts', \App\Post::lastUpdated()], function() use ($cache, $posts){   ?>
    <?php foreach($posts->get() as $post): ?>
        <?php $cache->cache($post, function() use ($post){ ?>
            <div class="column">
                <div class="ui fluid card">
                    <div class="image">
                        <img src="http://lorempicsum.com/futurama/290/150/<?= $post->id; ?>">
                    </div>
                    <div class="content">
                        <div class="header">
                            <?= $post->name; ?>
                        </div>
                        <div class="meta">
                            <?= $post->category->name; ?>, <?= $post->created_at; ?>
                        </div>
                        <div class="description">
                            <?= $post->excerpt; ?>
                        </div>
                    </div>
                </div>
            </div>
        <?php }); ?>
    <?php endforeach; ?>
<?php }); ?>

Nous n'avons pas ici à nous soucier de l'invalidation du cache, car lorsqu'un enregistrement va être modifié, la date de dernière mise à jour va automatiquement être changée, et le cache sera ainsi rafraîchi.

Redis et Memcached

Le principal problème de cette méthode est que l'on génère de nombreuses informations en cache, chaque fois qu'un enregistrement est modifié une nouvelle clé est générée et un nouvel enregistrement est mis en cache. Il faut donc disposer d'un processus capable de vider automatiquement la mémoire en supprimant les clefs les plus vieilles. Pour cela il est possible d'utiliser Redis ou Memcached qui permettent de sauvegarder des clés et qui disposent d'un algorithme LRU (least recently used) qui permet de supprimer les enregistrements les moins utilisés. Ainsi, dans notre code nous n'avons pas à nous soucier de l'invalidation, c'est le système de cache retenu qui s'en occupera.

<?php
namespace Grafikart;

use Predis\Client;

class RedisCache implements CacheInterface{

    private $redis;

    public function __construct(){
        $this->redis = new Client();
    }

    public function get($key)
    {
       return $this->redis->get($key);
    }

    public function set($key, $value)
    {
       return $this->redis->setex($key, 3600 * 24, $value); // Le cache expirera au bout d'une journée ou en cas de saturation de la mémoire
    }
}