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 :
FRONTENDqui applique les options au frontend ;BACKENDqui 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 variable →
boolé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.

