Edit : un nouvel article qui remplace le FIFO par un LRU pour la gestion de la taille du tampon (Implémentation d'un buffer LRU).
Cela fait un moment que je voulais rédiger un article sur cette astuce que j'utilise souvent dans mes scripts php. Et comme j'ai un peu le temps aujourd'hui j'en profite :D.
Qu'est ce qu'un buffer mémoire ?
Le tampon mémoire (ou buffer) est tout simplement un tableau associatif qui, en fonction d'une valeur de départ (index du tableau), donne une valeur d'arrivée (valeur du tableau pour l'index).
Le tableau permet d'obtenir par accès direct (à opposer à l'accès séquentiel des listes chainées) les correspondances entre deux valeurs.
Un exemple concret de ce type de tableau est un tampon qui pour un identifiant utilisateur nous donne son pseudonyme :
<?php
$buffer = array(
1 => 'petitchevalroux',
2 => 'patrick',
3 => 'pierre'
);
?>
Pourquoi utiliser un tampon mémoire ?
L'utilisation d'un tampon mémoire permet d'optimiser le code php en diminuant le nombre de requêtes en base de données.
En stockant dans le tableau indexé l'association clé valeur cela supprime les requêtes doublons.
Cas d'utilisation
Le cas d'utilisation que je vais prendre est assez simple et illustre facilement les bénéfices de l'utilisation d'un buffer.
Prenons comme exemple la page d'article d'un blog avec des commentaires le code pour afficher les commentaires de l'article pourrait ressembler à quelque chose du genre :
<?php
/*On récupère les commentaires de l'article courant*/
$comments = $db->select('SELECT USR_ID,COMMENT FROM COMMENTS WHERE ART_ID='.(int)$_GET['id']);
echo '<table>';
/*Pour chaque commentaire on affiche le pseudonyme du membre avec le contenu du commentaire*/
foreach ($comments as $comment)
{
echo '<tr><td>',getUserPseudo((int)$comment['USR_ID']),'</td><td>',$comment['COMMENT'],'</td></tr>';
}
echo '</table>';
?>
Je tiens à préciser que cet exemple est uniquement là pour illustrer la technique du tampon mémoire et n'est absolument pas le meilleur moyen d'afficher les commentaires d'un article. Cette précision faite regardons le code de la fonction getUserPseudo :
<?php
/**
* Retourne le pseudonyme d'un utilisateur en fonction de son identifiant
*
* @param integer $usrId
* @return string
*/
function getUserPseudo($usrId)
{
/*On récupére l'objet base de données*/
$db = Database::getInstance();
/*On fait la requete pour récupérer le pseudo*/
$data = $db->select('SELECT PSEUDO FROM USERS WHERE ID='.(int)$usrId.' LIMIT 1');
if(isset($data[0]) === false)
{
/*Si on trouve pas on retourne vide*/
return '';
}
/*Sinon on retourne le pseudo trouvé*/
return $data[0]['PSEUDO'];
}
?>
Le problème de cette fonction est que si le même utilisateur à posté plusieurs commentaires sur le même article, le code php va effectuer une requête par commentaire, ce qui peut vite surcharger le serveur mysql.
Fonction utilisant le tampon mémoire
Et maintenant voici le code de la fonction getUserPseudo qui utilise un tampon mémoire. La taille du tableau est volontairement limité à 1000 pour limiter l'empreinte mémoire du buffer et ne pas saturer la mémoire vive.
J'utilise le principe fifo pour limiter la taille du tampon ainsi que l'astuce de mon article : Effacer le premier élément d'un tableau indéxé
<?php
/**
* Retourne le pseudonyme d'un utilisateur en fonction de son identifiant
*
* @param integer $usrId
* @return string
*/
function getUserPseudo($usrId)
{
/*Tableau qui nous sert de tampon mémoire*/
static $buffer = array();
/*Entier qui contient le nombre d'elements du tampon*/
static $countBuffer = 0;
/*On crée un pointeur sur l'index du tableau*/
$pseudo = &$buffer[$usrId];
/*Si on a déjà le pseudo dans le tampon on le retourne sans refaire de requete en base de données*/
if(isset($pseudo) === true)
{
return $pseudo;
}
/*Si le tampon est plein on vire le premier élement avant d'ajouter le nouveau*/
if($countBuffer > 999)
{
list($firstIndex, ) = each($buffer);
unset($buffer[$firstIndex]);
}
/*Dans l'autre cas on incrémente le compteur*/
else
{
$countBuffer++;
}
/*On récupére l'objet base de données*/
$db = Database::getInstance();
/*On fait la requete pour récupérer le pseudo*/
$pseudo = $db->select('SELECT PSEUDO FROM USERS WHERE ID='.(int)$usrId.' LIMIT 1');
if(isset($pseudo[0]) === false)
{
$pseudo = '';
}
else
{
$pseudo = $pseudo[0]['PSEUDO'];
}
/*On retourne le pseudo*/
return $pseudo;
}
?>
L'avantage de cette fonction c'est qu'en utilisant le tampon fifo si un utilisateur à poster 300 commentaires sur l'article, le serveur de base de données n'aura qu'une seule requête à gérer.
Voilà c'est fait, il ne vous reste plus qu'à trouver d'autres cas d'utilisations dans vos propres scripts php, et personnellement j'en ai un bon paquet.
Image : Gadl