Cache-cache en php

Aujourd'hui, je vous propose de voir comment créer un système simple (simpliste ?) de cache pour votre site web.
On trouve plusieurs codes de ce genre sur le net, mais ils sont souvent trop élaborés à mon goût.
Je détaille ici tout le cheminement.

 

 


 

 

 

Ce cache devra :

  • être simple à utiliser,
  • permettre de stocker une page complète, une ou plusieurs parties d'une page ou même une variable,
  • n'avoir besoin d'aucune configuration, création de dossier etc...
  • gérer seul l'obsolescence des fichiers temporaires (le fichier est renouvelé au bout d'un certain temps),
  • permettre le vidage complet du cache ou l'effacement individuel des fichiers,
  • être efficace

Pour les impatients, le fichier c'est par là ;)



La fonction fondamentale d'un cache étant la sauvegarde et l'extraction de données du dossier temporaire, on commencera donc par créer deux fonctions:cache_write() pour écrire des données et cache_read() pour les lire.

Ces deux fonctions recevront en argument le nom du fichier temp à créer/lire.
La fonction cache_write() aura en outre besoin qu'on lui fournisse les données à stocker ainsi que la durée de vie du fichier à créer.
Afin d'éviter de se compliquer la vie avec des créations de dossier temp, des chemins variables etc, la fonction créera un dossier temp local s'il n'y en a pas.

Quant à la fonction cache_read(), elle devra évidemment vérifier l'existence du fichier avant de le charger et retourner false si le fichier n'existe pas: ainsi nous saurons s'il est nécessaire ou pas de créer le contenu.

function cache_read($fichier){
     if (file_exists('temp/'.$fichier)){ // on ne charge que si le fichier existe ()
          $donnees=file_get_contents('temp/'.$fichier); // si le fichier existe, on le charge
          return $donnees; 
     }else{return false;}
}

function cache_write($fichier,$donnees,$duree){ if (!is_dir('temp/')){mkdir ('temp');}//crée le /temp/ si nécessaire file_put_contents('temp/'.$fichier,$donnees); }

 


Toutefois, cache_write() ne permet pour l'instant que de stocker une chaine...
Si on veut pouvoir conserver le résultat d'une requête sql un peu longue par exemple, il faudra pouvoir stocker un array().
Pour cela, on doit le sérializer avant de l'enregistrer.

 

function cache_write($fichier,$donnees,$duree){
    if (!is_dir('temp/')){mkdir ('temp');}//crée le /temp/ si nécessaire
    if (is_array($donnees)){$donnees=serialize($donnees);}
    file_put_contents('temp/'.$fichier,$donnees);
}


Mais dans ce cas, il faudra que cache_read puisse unserializer les données au cas où.
Plutôt que de s'embêter à faire des test... on va simplement vérifier si unserialize renvoie autre chose que false en inhibant les erreurs (oui, je sais, c'est maaaaal!):

 

function cache_read($fichier){
    if (file_exists('temp/'.$fichier)){ // on ne charge que si le fichier existe ()
        $donnees=file_get_contents('temp/'.$fichier); // si le fichier existe, on le charge
        if ($donnees2=@unserialize($donnees)){$donnees=$donnees2;} // si unserialize renvoie un contenu, on remplace $donnees
        return $donnees; }else{return false;
    }// si pas de fichier dans le cache, on renvoie faux
}

 


Oui, mais et la variable $duree alors?!
Ben on va s'en occuper de suite: pour que le cache sache (ouch!) si le fichier est périmé ou pas, je propose de postdater le fichier.
La fonction touch() permet de fixer la date de modification d'un fichier.
Il suffira donc d'ajouter la $duree à l'heure de création et de touch() le fichier.
 

function cache_write($fichier,$donnees,$duree){
     if (!is_dir('temp/')){mkdir ('temp');}//crée le /temp/ si nécessaire
     if (is_array($donnees)){$donnees=serialize($donnees);}
     file_put_contents('temp/'.$fichier,$donnees);
     if ($duree!=0){$duree=@date('U')+(60*$duree);}// on multiplie par 60 pour pouvoir fournir une durée en minutes
     touch('temp/'.$fichier,$duree);
}


Date('U') renvoie le nb de secondes Unix. On y ajoute la durée en minutes x 60.
Pourquoi tester si la $duree est à 0 ?
On se servira d'une durée à 0 pour permettre la création de fichiers sans date de péremption.
La fonction cache_read() doit à présent savoir détecter si le fichier demandé est obsolète ou pas.

Créons une fonction qui vérifie si un fichier est obsolète et qui le détruit si c'est le cas:

function cache_is_obsolete($fichier){
   $dat=@filemtime('temp/'.$fichier); // on récup la date du fichier
   if (!file_exists('temp/'.$fichier)){return true;} // si le fichier n'existe pas, il est obsolète 
   if ($dat==0){return false;}// si la date est à 0: il n'est pas obsolète
   if ($dat<@date('U')){ // si la date du fichier est dépassée, il est obsolète 
      cache_delete($fichier); // on le vire 
        return true; // il était obsolète !
   }
   return false; // si on en est là, c'est que tout va bien, le fichier est toujours valide...
}


Bien sûr, il nous faut créer une fonction pour effacer le fichier:

function cache_delete($fichier){if (file_exists('temp/'.$fichier)){unlink ('temp/'.$fichier);}}


Il suffit d'ajouter le recours à cache_is_obsolete() dans cache_read() dans les conditions de validation:

function cache_read($fichier){
    if (file_exists('temp/'.$fichier)&&!cache_is_obsolete($fichier)){ // on ne charge que si le fichier existe () ou qu'il n'est pas obsolete
        $donnees=file_get_contents('temp/'.$fichier); // si le fichier existe, on le charge
        if ($donnees2=@unserialize($donnees)){$donnees=$donnees2;} // si unserialize renvoie un contenu, on remplace $donnees
        return $donnees; 
    }else{return false;}// si pas de fichier dans le cache, on renvoie faux
}

A ce stade, on peut écrire et lire des données dans le cache avec une date de péremption gérée automatiquement.
Mais comment collecter les données ?
Grâce aux fonctions php ob-start() et ob_get_clear() qui permettent de détourner les sorties vers une variable.
Je propose de créer une fonction cache_start() qui se contentera de faire un ob_start() (pour gagner en lisibilité lors de l'utilisation) et une fonction cache_end() pour récupérer les données, les traiter et les sauver.

function cache_start(){ob_start();}

function cache_end($fichier,$duree){&nbsp; // termine la mise en cache $donnees=ob_get_clean(); // on récupère le contenu cache_write($fichier,$donnees,$duree); // on le sauvegarde via cache_write() return $donnees; // on renvoie les données }


Là, on a presque terminé.
Pour se servir de nos fonctions on devra tester si cache_read() renvoie quelque-chose:
 

  • si oui, on echo ou on utilise les données du cache,
  • sinon on démarre le cache d'abord, puis on crée le contenu et on fait appel à cache_end() pour stocker les données.
if (!$contenu=cache_read('nom du fichier')){
     // pas dans le cache on crée (mais sinon, $contenu reçoit les données)
     cache_start();
     // tout le code php etc ici
     //  on sauve le fichier pour une durée de 30 minutes
     $contenu=cache_end('nom du fichier',30); /
}
echo $contenu; 

Si on a un tableau de données à stocker, c'est sensiblement pareil:

if (!$tableau=cache_read('nom du fichier')){
     // pas dans le cache on crée (mais sinon, $contenu reçoit les données)
     cache_start();
     // ici on crée le tableau 
     // on sauve le tableau dans le fichier pour une durée de 30 minutes 
     $tableau=cache_end('nom du fichier',30); 
}
// on utilise les données du tableau

Selon la portion de code qui génère le contenu, le gain peut être considérable.
Ainsi, j'ai utilisé cette méthode sur ce blog et voilà un avant après

Lorsque pluxml génère le cache
 

Une fois qu'il suffit d’accéder au cache, lors du second accès
 
 


Si vous voulez récupérer le fichier c'est par là ;)

 



Si vous voulez une autre méthode, un peu plus simple pour cacher des pages entières mais ne permettant pas de cacher plusieurs parties de pages séparément, jetez un oeil sur catswhocode ...

Ma version donne la possibilité de stocker par exemple la liste des articles de la catégorie, les derniers tweets, la liste des derniers articles etc séparément et de les réutiliser séparément.
C'est ce que j'ai fait sur ce site: un cache pour le bottom (les 4 colonnes de bas), un pour le menu (sans la partie "connerie") et un pour la liste d'articles de la catégorie.

 

❝ 2 commentaires ❞

1  qwerty le

Pour gagner les performances, n'oubliez pas de trifouiller le .htaccess. :
#mettre en cache les fichiers html et htm pour 30 minutes
<FilesMatch ".(html|htm|php)$">
Header set Cache-Control "max-age=0"
</FilesMatch>

Header unset ETag
FileETag None

# MOD_DEFLATE COMPRESSION
SetOutputFilter DEFLATE
AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/x-javascript application/x-httpd-php

#Pour les navigateurs incompatibles
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

#ne pas mettre en cache si ces fichiers le sont déjà
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip

#les proxies doivent donner le bon contenu
Header append Vary User-Agent env=!dont-vary

<Files ~ "^\.(htaccess|htpasswd)$">
deny from all
</Files>
#pages d'erreurs
Options All -Indexes
ErrorDocument 400 /erreur.php?erreur=400
ErrorDocument 401 /erreur.php?erreur=401
ErrorDocument 402 /erreur.php?erreur=402
ErrorDocument 403 /erreur.php?erreur=403
ErrorDocument 404 /erreur.php?erreur=404
ErrorDocument 405 /erreur.php?erreur=405
ErrorDocument 406 /erreur.php?erreur=406
ErrorDocument 407 /erreur.php?erreur=407
ErrorDocument 408 /erreur.php?erreur=408
ErrorDocument 409 /erreur.php?erreur=409
ErrorDocument 410 /erreur.php?erreur=410
ErrorDocument 411 /erreur.php?erreur=411
ErrorDocument 412 /erreur.php?erreur=412
ErrorDocument 413 /erreur.php?erreur=413
ErrorDocument 414 /erreur.php?erreur=414
ErrorDocument 415 /erreur.php?erreur=415
ErrorDocument 416 /erreur.php?erreur=416
ErrorDocument 417 /erreur.php?erreur=417
ErrorDocument 500 /erreur.php?erreur=500
ErrorDocument 501 /erreur.php?erreur=501
ErrorDocument 502 /erreur.php?erreur=502
ErrorDocument 503 /erreur.php?erreur=503
ErrorDocument 504 /erreur.php?erreur=504
ErrorDocument 505 /erreur.php?erreur=505

#video HTML5
AddType video/ogg .ogv
AddType video/mp4 .mp4
AddType video/webm .webm
AddType text/cache-manifest .manifest

 
2  bronco le

Voilà une excellente intervention en effet ! Merci

 

Fil RSS des commentaires de cet article

✍ Écrire un commentaire

Inutile de poster un commentaire à la con pour vous faire de la pub, ce sera filtré et dégagé direct...

Quelle est la première lettre du mot cfjag ?