Manuel de référence → Hoa_Cache

Chapitre 6. Hoa_Cache

Quand on conçoit une application, la plupart des données calculées utilisent de la ressource auprès du processeur, de la mémoire, etc. Si on travaille sur une application traitant des milliers, voire des millions de tâches à la minute, enregistrer les résultats temporairement sera un gain énorme ; d'où l'importance d'un système de cache.

Fonctionnement général

Le paquetage Hoa_Cache est composé de deux parties : le frontend (frontale) et le backend (arrière). Ces deux parties peuvent être configurées pour s'adapter aux besoins de l'utilisateur.

Hoa_Cache dispose d'une fabrique de caches pour assembler ces deux parties.

Fabrique de caches

La fabrique de caches spécifie quels types de données on doit enregistrer, et sous quelles formes. Autrement dit, la fabrique définit quelle partie frontale et arrière nous préférons utiliser. On peut en profiter pour lui soumettre des paramètres (optionnels).

Un premier exemple serait le suivant : on souhaite capturer toutes les données de sortie (lancées depuis echo par exemple), et les enregistrer dans des fichiers, alors :

<?php
 
set_include_path( ... );
require_once 'Framework.php';
 
import('Cache.~');
 
$cache = Hoa_Cache::factory('Output', 'File');

On a alors un système de cache, une instance de cache, dans la variable $cache, qui permet d'attraper les sorties et de les enregistrer dans des fichiers. Il ne nous reste qu'à baliser les zones que l'on veut mettre en cache.

Baliser les zones de caches

Le balisage de zones à mettre en cache se fait à l'aide d'une structure de contrôle (if), et de deux méthodes : start et end.

Chaque enregistrement est repéré par un identifiant qui doit être unique. On a la possibilité d'ajouter des variables d'environnements — qui représentent l'état de l'application à un moment t — à l'identifiant que l'on passe manuellement à la méthode start. On aura alors :

$cache = Hoa_Cache::factory('Output', 'File');
 
if($cache->start('monID')) {
 
    echo 'Je suis une sortie.' . "\n";
 
    $cache->end();
}

Si le cache n'existe pas, la valeur s'affiche normalement, puis on crée le cache, avec la valeur enregistrée à l'intérieur ; sinon on récupère sa valeur, l'affiche et saute le bloc d'instructions.

Nettoyer les caches

Les caches ont une durée de vie de 3600 secondes par défaut. On a toujours la possibilité de la modifier en utilisant les options du frontend. Si certains caches sont périmés et ne sont plus utilisés, il est bon de nettoyer le dossier qui contient tous les caches. On utilise pour ça la fonction clean qui ne prend qu'un seul paramètre : $lifetime, i.e. la durée de vie.

On peut nettoyer de 4 façons différentes :

  • la constante CLEANING_ALL, nettoie tous les fichiers même s'ils ne sont pas expirés. De cette façon, ils seront tous recréés ;
  • la constante CLEANING_EXPIRED, nettoie seulement les fichiers expirés. La date d'expiration vaut soit la valeur par défaut, soit la valeur passée au frontend ;
  • la constante CLEANING_USER, n'a de sens que pour la partie backend APC ;
  • la valeur directement passée à la fonction (en seconde).

Toutes les constantes ne fonctionnent pas avec tous les types de cache. Une exception Hoa_Cache_Exception sera jetée si jamais.

Le nettoyage des caches se fait automatiquement à chaque fois que la fonction start est appelée. La durée de vie des caches à nettoyer est donnée par la valeur passée au frontend (par défaut, 3600). On n'a donc — en théorie — pas besoin de nettoyer nous même, sauf cas particulier.

On nettoierait de cette façon :

/**
 * On nettoie tous les caches dont la valeur
 * d'expiration vaut la valeur donnée au frontend.
 */
$cache->clean();
 
/**
 * On nettoie tous les caches expirés.
 */
$cache->clean(Hoa_Cache::CLEANING_EXPIRED);
 
/**
 * On nettoie tous les caches vieux d'une minute.
 */
$cache->clean(60);

Supprimer les caches

On peut avoir besoin de supprimer un cache. Pour ça, Hoa_Cache propose la fonction remove qui prend en paramètre l'identifiant du cache.

Pour des raisons de sécurité, l'identifiant doit être l'identifiant du fichier cache, et pas du bloc. Ainsi, le code suivant n'aura aucun effet :

if($cache->start('monID')) {
 
    echo date('H:i:s', time());
    $cache->end();
}
 
$cache->remove('37a468b3384fb0b306f0f741afdde68f');

Si on veut supprimer tous les caches, il est préférable — on l'aura deviné — d'utiliser la méthode clean avec la constante CLEANING_ALL :

$cache->clean(Hoa_Cache::CLEANING_ALL);

Nettoyer tous les caches revient en fait à nettoyer tous les caches dont la durée de vie vaut -1. C'est ce que fait la constante CLEANING_ALL.

Définir les options

On a parlé de soumettre deux paramètres à la fabrique respectivement pour le frontend et le backend. On s'y prend de cette façon :

$frontendOptions = array([ ... ]);
$backendOptions  = array([ ... ]);
 
$cache = Hoa_Cache::factory('Output', 'File',
                            $frontendOptions, $backendOptions);

Utiliser un tableau vide revient à laisser le système utiliser les paramètres par défaut.

Si jamais on a envie de changer les paramètres en cours d'exécution (par exemple, avant un nouveau balisage), on peut utiliser la méthode setOptions qui prend en premier argument la partie qui va recevoir les options, et en second argument, les options dans un tableau.

Pour faciliter son utilisation, on introduit deux constantes :

  • FRONTEND qui applique les options au frontend ;
  • BACKEND qui applique les options au backend.

On aurait alors :

$nouvellesOptions = array([ ... ]);
$autresOptions    = array([ ... ]);
 
/**
 * On redéfinit les paramètres du frontend.
 */
$cache->setOptions(Hoa_Cache::FRONTEND, $nouvellesOptions);
 
/**
 * On redéfinit les paramètres du backend.
 */
$cache->setOptions(Hoa_Cache::BACKEND, $autresOptions);

Frontend du cache

On a vu précédemment que le cache était constitué de 2 parties : la première est le frontend. Il va définir la provenance des données à mettre en cache.

On peut capturer 3 types de sources de données : l'une depuis une classe, l'autre depuis une fonction, et enfin, la dernière depuis une sortie.

Cache depuis une classe

Quand on écrit des objets qui effectuent de lourds traitements sur des données, il est toujours bon de ne pas re-traiter ces données si elles sont identiques. C'est ici que l'on fait intervenir le Hoa_Cache_Frontend_Class.

Son utilisation est des plus triviales. Il suffit de créer, à l'aide de la fabrique, une instance de cache pour les classes. On enregistre notre objet auprès de cette instance, et on appelle nos méthodes, avec leurs paramètres sur cette instance de cache comme si c'était l'instance de notre objet. Voyons plutôt :

/**
 * Notre classe à mettre en cache.
 */
class MaClass {
 
    public function maMethode ( $param ) {
 
        echo strtolower($param) . "\n";
 
        return 'un retour (' . $param . ')';
    }
}
 
/**
 * La fabrique nous renvoie une instance de
 * cache pour les classes.
 */
$cache = Hoa_Cache::factory('Class', 'File');
 
/**
 * On définit l'objet MaClasse comme étant celui
 * qui sera observé.
 */
$cache->setObject('MaClasse');
 
/**
 * On appelle la fonction de façon naturelle.
 */
$retour = $cache->maMethode('Bonjour tout le monde !');
echo 'Le retour est : ' . $retour;
 
// Affichera :
//     bonjour tout le monde !
//     Le retour est : un retour (Bonjour tout le monde !)

Si la méthode a été exécutée et que son cache existe, alors on lit le cache et on sort les données nécessaires, sinon on créé le fichier cache. Ce système prend en compte les paramètres (et heureusement), i.e. l'appel de la fonction maMethode avec en paramètre Hello, world ! ne donnera pas le même résultat — ce qui est un comportement tout à fait normal —.

L'appel des méthodes statiques se fait à travers la liaison dynamique de PHP, i.e. en utilisant le symbole -> :

class MaClass {
 
    public static function maMethodeStatique ( $param ) {
 
        echo strtolower($param) . "\n";
 
        return 'un retour statique (' . $param . ')';
    }
}
 
// Même code que précédemment.
 
$retour = $cache->maMethodeStatique('Hopla');

On ne fait pas la différence entre les méthodes statiques et dynamiques lors de l'appel, mais la différence est belle et bien là lors de l'exécution.

On fera très attention à ce que les objets soient sérialisables. On peut implémenter l'interface Serializable pour s'en assurer. Il sera utile de définir les méthodes de cette interface dans le cas d'association ou chose du genre.

Cache depuis une fonction

Si on ne travaille pas avec des classes, mais avec des fonctions, Hoa_Cache est également capable de mettre en cache des fonctions à l'aide de Hoa_Cache_Frontend_Function. Son comportement est semblable à Hoa_Cache_Frontend_Class à l'exception qu'on ne définit pas d'objet. On appelle donc les fonctions sur l'instance du cache de façon tout à fait naturelle :

/**
 * Notre fonction à mettre en cache.
 */
function maFonction ( $param ) {
 
    echo 'Mon nom est ' . $param . "\n";
 
    return strtolower($param);
}
 
/**
 * La fabrique nous renvoie une instance de
 * cache pour les fonctions.
 */
$cache = Hoa_Cache::factory('Function', 'File');
 
/**
 * On appelle la fonction de façon naturelle.
 */
$retour = $cache->maFonction('Bob');
echo 'Le retour est : ' . $retour;
 
// Affichera :
//     Mon nom est : Bob
//     Le retour est : bob

On voit également l'utilité lors d'une requête SQL un peu lourde :

$cache    = Hoa_Cache::factory('Function', 'File');
$resultat = $cache->mysql_query($grosseRequeteSQL) or false;

On peut également éviter de gros traitements :

$cache     = Hoa_Cache::factory('Function', 'File');
$resultat  = mysql_query($requete);
 
if($valeur = $cache->mysql_fetch_assoc($resultat)) {
 
    echo $valeur[ ... ] . "\n";
}

Tous les traitements sont évitables.

Cache depuis une sortie

La capture des sorties a été vue dans la section intitulée « Baliser les zones de caches ». On utilise les deux méthodes start et end, du paquetage Hoa_Cache_Frontend_Output pour délimiter la zone où les données sortiront. Un identifiant unique doit être renseigné à la fonction start :

/**
 * La fabrique nous renvoie une instance de
 * cache pour les sorties.
 */
$cache = Hoa_Cache::factory('Output', 'File');
 
if($cache->start('monIDUnique')) {
 
    echo date('H:i:s', time()) . "\n";
 
    $cache->end();
}
 
echo date('H:i:s', time());

Options du frontend

Lors de la demande d'instance de caches auprès de la fabrique de caches, il est possible de passer des paramètres pour contrôler le comportement du frontend. Ces paramètres sont rangés dans un tableau :

$frontendOptions = array([ ... ]);
 
$cache = Hoa_Cache::factory('Output', 'File', $frontendOptions);

Passer le tableau vide correspond à utiliser les paramètres par défaut.

On présente les différents paramètres du frontend.

Durée de vie d'un cache

Chaque fichier cache a une durée de vie qui se quantifie en secondes. Par défaut, la durée de vie d'un cache est 3600 secondes.

La clé du tableau pour modifier la durée de vie est lifetime :

$frontendOptions = array(
    'lifetime' => 600
);

Sérialisation du contenu

Ce paramètre concerne la façon dont les données sont conservées dans les fichiers cache. Par défaut, il vaut true, i.e. les données seront sérialisées à tout les coups.

La clé du tableau pour modifier la façon dont les données seront conservées est serialize_content :

$frontendOptions = array(
    'serialize_content' => false
);

État de l'application et ID

On a vu que la validité d'un cache ne tenait pas qu'à son identifiant, mais aussi de l'état de son environnement, de l'état de l'application, i.e. des valeurs des différents tableaux GET, POST, etc.

Par défaut, les identifiants sont construits en prenant compte des variables GET, POST, COOKIE, SESSION, et FILES, soit toutes les variables. La clé du tableau est : make_id_with, et prend en valeur un tableau associatif variablebooléen :

$frontendOptions = array(
    'make_id_with' => array(
        'get'      => true,
        'post'     => true,
        'cookie'   => false
    )
);

Si le tableau n'est pas complété totalement, il sera rempli avec les valeurs par défaut.

L'identifiant final du cache est la valeur de toutes les clés et de toutes les valeurs associées aux clés, ajouté à l'identifiant que l'on passe manuellement, le tout crypté en MD5.

Backend du cache

Avec le frontend du cache, on a vu comment capturer des données. Maintenant qu'elles sont capturées, il faut les enregistrer quelque part : c'est le rôle du backend.

Les données peuvent être stockées sous forme de fichier, sous forme de données APC, ou Memcache.

Cache vers des fichiers

Pour que les données de cache soient enregistrées dans un fichier, il faut déclarer le backend de cette façon :

$cache = Hoa_Cache::factory('Output', 'File');

Les fichiers auront comme nom l'identifiant du cache crypté en MD5 (voir la section intitulée « État de l'application et ID »). L'extension des fichiers est .cache.

Par défaut, les fichiers sont enregistrés dans /tmp/. Il est possible de changer ce chemin à travers l'option cache_directory. On s'y prendra de la façon suivante :

// On laisse les paramètres par défaut.
$frontendOptions = array();
 
/**
 * On redéfinit le chemin où sont enregistrés les fichiers.
 */
$backendOptions = array(
    'cache_directory' => './MonDossierCache/'
);
 
$cache = Hoa_Cache::factory('Output', 'File',
                            $frontendOptions, $backendOptions);

Dans le cas où on a un grand nombre de fichiers, il peut être appréciable de les compresser. La compression s'effectue en utilisant le format de données ZLIB. Pour plus de détails sur l'algorithme de compression, on peut lire la RFC 1950 : ZLIB Compressed Data Format Specification version 3.3. Pour cela, on a besoin de connaître le niveau de compression que l'on souhaite. Ce niveau va de 0 pour aucune compression, à 9 pour la compression la plus forte, celle par défaut.

On utilise donc l'option compress qui est un tableau associatif pour l'activation et le niveau de compression souhaité :

// On laisse les paramètres par défaut.
$frontendOptions = array();
 
/**
 * On redéfinit le niveau de compression.
 */
$backendOptions = array(
    'compress'   => array(
        'active' => true,
        'level'  => 7
    )
);
 
$cache = Hoa_Cache::factory('Output', 'File',
                            $frontendOptions, $backendOptions);

Les options qui ne sont pas mentionnées sont automatiquement complétées par les valeurs par défaut.

On peut très bien imaginer qu'on veuille qu'un cache soit compressé et pas un autre :

/**
 * On récupère une instance de cache.
 */
$cache = Hoa_Cache::factory('Output', 'File');
 
/**
 * Ce premier balisage sera compressé.
 */
if($cache->start('Test')) {
 
    echo 'Ceci est un test';
 
    $cache->end();
}
 
/**
 * On désactive la compression pour les autres
 * fichiers.
 */
$cache->setOptions(Hoa_Cache::BACKEND, array(
                      'compress' => array(
                          'active' => false
                      )
                  ));
 
/**
 * La suite ne sera pas compressée.
 */
if($cache->start( ... )) {
 
    // …
 
    $cache->end();
}

Ainsi, on peut redéfinir les options durant l'exécution, et placer les fichiers de cache à différent endroit, sous différentes formes etc.

On notera néanmoins que la compression ajoute un temps d'accès aux données. On notera également que le module de compression étant écrit en C est très performant, ce temps d'accès est donc négligeable sauf pour de très gros fichiers caches, i.e. plusieurs milliers de kilos octets.

Cache vers APC

Le cache PHP alternatif est un cache opcode libre et ouvert pour PHP. Il a été conçu pour fournir un gestionnaire ouvert et robuste pour mettre en cache et optimiser le code intermédiaire de PHP ; nous dit la documentation PHP à propos d'APC. On conseille de lire sa documentation pour plus d'informations, car on ne le développera pas ici. Un conseil cependant, l'extension APC est une extension PECL et n'est pas intégrée à PHP. Si elle n'est pas chargée, une exception Hoa_Cache_Exception sera jetée.

On précise à Hoa_Cache d'utiliser l'extension APC de cette façon :

$cache = Hoa_Cache::factory('Output', 'Apc');

Les données peuvent être sérialisées mais pas compressées (le traitement des données étant délégué à APC).

On a précisé que la constante CLEANING_USER pour la méthode clean n'avait de sens que pour le backend APC. Si on choisit cette constante, alors le cache utilisateur sera nettoyé, sinon ce sera le système entier qui sera nettoyé.

Cache vers Memcache

Le module Memcache fournit une interface procédurale maniable ainsi qu'orientée objet (celle utilisée) à Memcache, qui est un démon fortement efficace dans la gestion du cache, principalement destiné à faire baisser la charge des bases de données dans les applications web dynamiques ; nous dit la documentation PHP à propos de Memcache. On conseille aussi de lire sa documentation. Le module Memcache est également une extension de PECL, donc n'est pas intégré à PHP. Si le module n'est pas chargé, une exception Hoa_Cache_Exception sera jetée.

On l'utilise de cette façon :

$cache = Hoa_Cache::factory('Class', 'Memcache');

Il faut bien comprendre que le frontend appelle le backend, donc on ne travaille pas directement sur le backend, mais à travers les méthodes du frontend choisi. La seule chose à faire est donc de sélectionner quel backend on utilise.

Construire ses propres backend

Tous les backends sont construits autour de la classe abstraite Hoa_Cache_Backend_Abstract. Donc tous les backends étendent cette classe abstraite, et force la définition des méthodes load, save, clean, et remove. Un coup d'œil dans le manuel API pourra aider. On ne développe pas le sujet ici.