Les fonctions de manipulation du tampon de sortie (buffer)

Jean-François Lépine

La gestion de la Bufferisation de sortie peut bien souvent simplifier le développement d'applications web, en permettant de retravailler l'ensemble des données qui sont envoyées au navigateur du client.

Même si ce n'est pas son but premier, il est possible, par exemple, de modifier le contenu de la balise <head>, pour appeler un fichier CSS ou ajouter un script, ce n'importe où dans le code PHP.

Commentez

- Titre : Les fonctions de manipulation du tampon de sortie (buffer)

- Auteur : Jean-François Lépine

- Parution : 1 avril 2009

- Mise à jour : 9 décembre 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Jean-FranÃ§ois LÃ©pine. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Présentation

- I-B. Prérequis

- II. Première approche

- II-A. Les fonctions de Bufferisation de sortie

- II-B. Exemple : remplacer du texte

- II-C. Comment procéder ?

- II-D. Quoi faire et comment ?

- III. Les principales méthodes

- III-A. Point de départ : la Classe

- III-B. Savoir si le script est déjà utilisé dans la page

- III-C. Gérer les variables de script et les identifiants des objets HTML

- III-D. Modifier le Buffer de sortie pour ajouter un script

- III-E. Modifier le buffer de sortie pour ajouter un appel de fichier

- III-F. Conclusion sur la classe principale

- IV. Cas pratique : une miniature HighSlide

- IV-A. Introduction

- IV-B. La Classe JS_Generate_Highslide

- IV-C. Installer HighSlide sur une page HTML

- IV-D. Une miniature en une ligne de code

- V. Conclusion et fichiers

- V-A. Conclusion

- V-B. Fichiers et ressources

- V-C. Remerciements

I - Introduction

I-A - Présentation

PHP peut être utilisé pour automatiser un certain nombre de tâches du programmeur, par exemple l'inclusion de fichiers CSS ou JavaScript dans l'en tête du document HTML :

<html>
<head>
<title>...</title>
<!-- Liste d'inclusion CSS -->
<!-- Liste d'inclusion JavaScript -->
</head>
<body>...</body>
</html>

Lors du développement d'une application Web, ces listes d'inclusion changent fréquemment, ce qui oblige le programmeur à modifier fréquemment du code qui n'a aucune utilité fonctionnelle.

Nous pouvons utiliser PHP pour créer cette liste d'inclusion une fois que l'ensemble du document HTML est généré : c'est du post processing, PHP le permet grâce aux fonctions de mise en tampon du flux de sortie. Certains frameworks JavaScript proposent cette fonctionnalité, mais cela oblige à faire davantage d'échanges en HTTP et cela retarde ainsi l'affichage du document dans le navigateur. Ce traitement serait effectué bien plus rapidement en PHP avec la gestion du tampon de sortie.

La gestion de la Bufferisation de sortie peut ainsi bien souvent simplifier le développement d'applications PHP. Comment gérer les identifiants uniques de la page ? Comment ajouter un fichier css à n'importe quel moment (même après que l'on ait fermé la balise head ? Je vous propose de développer ensemble une classe qui nous permettra de gérer tout cela, grâce aux fonctions de Gestion du Buffer de sortie.

À titre d'exemple, nous construirons ensemble une première classe fille de création de miniatures zoomables avec Highslide.

I-B - Prérequis

Pour bien comprendre ce tutoriel, il convient de connaître les concepts de la Programmation Orientée Objet (POO), et plus précisément les notions de classe abstraite et d'héritage.

Toutefois, pour un simple aperçu des fonctions de Gestion de la Bufferisation en PHP, vous pouvez vous contenter de la Section II - La Bufferisation, qui sera très facilement compréhensible par tous.

II - Première approche

II-A - Les fonctions de Bufferisation de sortie

Le buffer c'est ce flux d'informations que votre serveur envoie vers le navigateur du client.

Lorsque vous vous écrivez :

echo 'Hello World!';

vous ajoutez au buffer la chaine de caractères « Hello World! ».

Il est possible, dans une certaine mesure, de contrôler ce buffer. Cela peut être très pratique, notamment lors de l'utilisation de la fonction header() au milieu de notre code.
En effet, en vidant le buffer de la page avant d'effectuer le header(), vous pourrez utiliser cette fonction n'importe où dans votre code.

//
// exemple de redirection sans la gestion du buffer

//
// écrire dans notre page
echo 'ceci est du texte';

//
// rediriger le visiteur
header("Location: http://www.developpez.com");

// => ERREUR

la redirection ne marche pas : il y a une alerte parce que des entêtes ont déjà été envoyés au navigateur.

Maintenant, utilisons les fonctions de gestion de la Bufferisation de sortie :

//
// exemple de redirection AVEC la gestion du buffer

//
// démarrer la temporisation
ob_start();

//
// écrire dans notre page
echo 'ceci est du texte';

//
// vider le contenu du buffer
ob_clean();

//
// rediriger le visiteur
header("Location: http://www.developpez.com");

// => tout est OK

Cette fois, aucune erreur : nous avons vidé le buffer avant d'effectuer notre redirection.

La gestion de la Bufferisation s'effectue avec la fonction ob_start(). Cette fonction temporise l'envoi du flux (du buffer), de telle sorte qu'on puisse envoyer le buffer d'un seul coup, soit à la fin de notre code (ce qui se fait automatiquement), soit à un moment de notre choix.

Voici la liste des principales fonctions de gestion de la Bufferisation de sortie :

Fonction

Description

ob_start()

Démarre la temporisation du flux. À placer en début de code

ob_get_contents()

Retourne le buffer (temporisé)

ob_clean()

Vider le buffer. Utile, par exemple, pour utiliser la fonction header()

ob_get_clean()

Retourne le buffer et le vide. Équivalent de ob_get_contents() + ob_clean()

ob_flush()

Envoie le buffer (temporisé)

Examinons maintenant ce qu'il est possible de faire grâce à ces fonctions, en développant une petite Classe de génération de scripts JavaScript.

II-B - Exemple : remplacer du texte

Imaginons que nous voulions, pour une raison ou une autre, remplacer toutes les occurrences de « toto » sur la page par « titi » :

//
// démarrer la temporisation
ob_start();

...

//
// Notre page contient le mot "toto"
echo 'Hello toto !';

//
// récupérer le tampon de sortie et le vider
$content    = ob_get_clean();

//
// réécrire le contenu de la page
$content    = str_replace('toto', 'titi', $content);
echo $content;

La page affichera :

Hello titi !

Nous avons capturé le buffer et l'avons modifié selon nos souhaits. Cela peut être très pratique, par exemple pour ajouter des balises alt et title à toutes les images de la page qui n'en ont pas…

II-C - Comment procéder ?

Pour pouvoir réutiliser facilement notre code, nous allons développer une classe. Notre premier souci doit être la facilité d'utilisation de cette classe, le second sa compatibilité avec à peu près n'importe quel script ou framework JavaScript.

La solution que je vous propose de tester est une solution parmi d'autres, et il est possible d'en imaginer de plus efficaces ou plus rapides. Toutefois, notre solution nous permettra de comprendre comment fonctionnent les fonctions de Gestion de buffer de PHP.

Nous allons donc créer une classe générique qui va donner les méthodes principales de toutes les classes filles qui vont hériter d'elle. Cette Classe, jamais instanciée, sera donc abstraite.

Par ailleurs, il est possible d'imaginer qu'on utilise plusieurs fois le même script sur la page (dans le cas, par exemple, de miniatures pour un album photo). Les propriétés et attributs de cette Classe (identifiants des objets HTML, répertoires…) devront donc être partagés par l'ensemble des instances de cette Classe, c'est pourquoi nos méthodes et attributs seront Static.

II-D - Quoi faire et comment ?

Quelles sont les méthodes principales que nous aimerions avoir pour nous simplifier la gestion des scripts JavaScript dans nos fichiers PHP ?

Nous pouvons déterminer quatre points, communs à tous (ou presque) les scripts JavaScript, qui sont indispensables pour la génération de code JavaScript depuis le PHP :

- savoir si le script est déjà initialisé dans la page ;

- gérer les variables des scripts (identifiants des objets HTML, configuration, etc.) ;

- ajouter dans la balise head de notre code un appel de fichier (.css ou .js) à n'importe quel moment ;

- ajouter dans la balise head un script JavaScript.

Nous allons donc voir comment proposer une solution pour chacun d'entre eux.

III - Les principales méthodes

III-A - Point de départ : la Classe

Commençons par créer notre Classe. Nous avons dit qu'elle devait être abstraite, ce qui donne le fichier JS_Generate :

abstract class JS_Generate {

}

III-B - Savoir si le script est déjà utilisé dans la page

Maintenant, notre souci va être d'éviter d'inclure plusieurs fois les mêmes fichiers (css, js) ou de faire plusieurs fois les mêmes initialisations de scripts dans nos pages.

Nous allons créer une méthode isAlreadyUsed qui nous retournera un booléen pour indiquer cet état :

abstract class JS_Generate {
/*
* VARIABLES
* ____________________________________________ */
private static $_isAlreadyUsed    = false;     // le script a-t-il déjà été appelé dans la page

/*
* ACCESSEURS
* ____________________________________________ */

/** savoir si le script a déjà été appelé dans la page
* @return    boolean    isAlreadyUsed
* ------------------------------------------------------
*/
public static function isAlreadyUsed () {
return self::$_isAlreadyUsed;
}
/** dire si le script a déjà été appelé dans la page
* @param    boolean    isAlreadyUsed
* ------------------------------------------------------
*/
public static function setIsAlreadyUsed($var = null) {
if(is_null($var) || empty($var) || !is_bool($var))    return false;
self::$_isAlreadyUsed = $var;
}
}

On peut noter que la variable est Static ainsi que ses accesseurs. En effet, cette variable sera commune à toutes les instances de la classe utilisée.

III-C - Gérer les variables de script et les identifiants des objets HTML

Imaginons une galerie photo, constituée d'un album de 10 photos, toutes zoomables. Chaque identifiant étant unique, comment gérer les identifiants, directement depuis PHP, de telle sorte qu'il n'y ait jamais de doublon ?

Ajoutons une nouvelle variable, $_id, que nous incrémenterons à chaque utilisation.

abstract class JS_Generate {
/*
* VARIABLES
* ____________________________________________ */
private static $_id                = 0;        // facile la gestion des id pour les objets HTML

/*
* ACCESSEURS
* ____________________________________________ */

/** obtenir l'id du dernier objet HTML utilisé
* @return    string    _id
* ------------------------------------------------------
*/
public static function getId () {
return self::$_id;
}
/** définir l'id du dernier objet HTML utilisé
* @param    string    _id
* ------------------------------------------------------
*/
public static function setId($var = null) {
if(is_null($var) || empty($var) || !is_string($var))    return false;
self::$_id = $var;
}
}

Les paramètres de scripts (couleurs, effets…) doivent être aussi gérés par notre classe. Nous pouvons utiliser un tableau qui contiendra l'ensemble de ces variables de configuration.

Parmi ces paramètres de script, il est fréquent de devoir déclarer le répertoire contenant les fichiers du script lors de l'initialisation. Nous allons donc ajouter une variable $_dir pour gérer ce répertoire.

abstract class JS_Generate {
/*
* VARIABLES
* ____________________________________________ */
private static $_config            = array();    // configuration commune à tous les objets de la classe
private static $_dir            = 0;        // chemin par défaut à utiliser

/*
* ACCESSEURS
* ____________________________________________ */

/**
* récupérer le répertoire du script
* @return    string    le répertoire du script
* ------------------------------------------------------
*/
public static function getDir () {
return self::$_dir;
}
/** définir le répertoire du script
* @param    string    le répertoire du script
* ------------------------------------------------------
*/
public static function setDir($var = null) {
if(is_null($var) || empty($var) || !is_string($var))    return false;
self::$_dir         = $var;
}

/** obtenir la configuration courante
* @return    string    element de la config
* ------------------------------------------------------
*/
public static function getConfig ($elem) {
$tConfig    = self::$_config;
if(!isset($tConfig[$elem])) return false;
return $tConfig[$elem];
}
/** définir la configuration courante
* @param    string    _config
* ------------------------------------------------------
*/
public static function setConfig($elem = null, $var = null) {
if(is_null($elem) || empty($elem) || !is_string($elem))    return false;
if(is_null($var) || empty($var) || !is_string($var))    return false;
self::$_config[$elem] = $var;
}
}

III-D - Modifier le Buffer de sortie pour ajouter un script

Nous allons voir maintenant un ensemble de fonctions très pratiques : les fonctions de gestion du Buffer de sortie, préfixées ob_.

Grâce à elles, nous allons bloquer le flux de sortie (l'écriture de la page) avec ob_start(), pour pouvoir modifier ce contenu, et l'envoyer au poste client au moment de notre choix (ob_flush).

Créons notre page index.php, avec ce contrôle de flux :

//
// démarrer la temporisation du flux de sortie
ob_start();

//
// fichier requis
require_once 'class/JS_Generate.php';

//
// notre page simplifiée (sans doctype, etc.)
echo '<html><head></head><body>';

Nous allons pouvoir maintenant contrôler notre flux de manière efficace.

Il peut être utile d'insérer un script complet entre les balises head de la page. La méthode addScript qui suit est un exemple simple de modification du buffer :

/**
* ajouter un script spécifique à notre header
* @param    string    contenu
* @param    boolean    échec / réussite
* ------------------------------------------------------
*/
public static function addScript($strInsert) {
//
// contrôler
if(is_null($strInsert)     || empty($strInsert)     || !is_string($strInsert))    return false;

//
// ajouter les balises scripts
$strInsert    = '<script language="javascript" type="text/javascript">'
. $strInsert
. '</script>';

//
// chercher dans le buffer de sortie la balise </head
// on va ajouter juste avant notre appel de fichier
$content        = ob_get_contents();
$regex            = "/\<\/head\>/si";
$content        = preg_replace($regex, $strInsert."\n".'</head>', $content);

//
// vider le contenu du buffer pour ajouter celui qu'on vient de modifier (variable $content);
ob_clean();
echo $content;

//
// confirmer que tout s'est bien passé
return true;

}

Notez qu'il aurait été possible d'utiliser la fonction ob_get_clean() à la place de la fonction ob_get_content(), ce qui nous aurait permis d'éviter de faire appel à la fonction ob_clean(). Toutefois, dans notre exemple nous procéderons à chaque fois en deux temps (récupérer puis vider le buffer) pour une plus grande clarté.

III-E - Modifier le buffer de sortie pour ajouter un appel de fichier

Créons une méthode qui va utiliser cette gestion du buffer pour ajouter un fichier à l'entête de la page HTML.

/**
* ajouter un fichier js à notre entete
* @param    string    url du fichier
* @param    boolean    échec / réussite
* ------------------------------------------------------
*/
public static function addFile($url) {
//
// contrôler
if(is_null($url)     || empty($url)     || !is_string($url))    return false;

$strInsert    = "\n".'<script language="javascript" type="text/javascript" src="'.$url.'"></script>';

//
// chercher dans le buffer de sortie la balise head
// on va ajouter juste avant notre appel de fichier
$content        = ob_get_contents();
$regex            = "/\<\/head\>/si";
$content        = preg_replace($regex, $strInsert."\n".'</head>', $content);

//
// vider le contenu du buffer pour ajouter celui qu'on vient de modifier (variable $content);
ob_clean();
echo $content;

//
// confirmer que tout s'est bien passé
return true;
}

C'est bien, mais on pourrait vouloir plus. Par exemple, ajouter un fichier .CSS plutôt qu'un fichier .JS.

Utilisons pour cela les fonctions parse_url et pathinfo, qui vont nous permettre de récupérer l'extension de l'url passée en paramètres.

/**
* ajouter un fichier css / js à notre entete
* @param    string    url du fichier
* @param    boolean    échec / réussite
* ------------------------------------------------------
*/
public static function addFile($url) {
//
// contrôler
if(is_null($url)     || empty($url)     || !is_string($url))    return false;

//
// récupérer l'extension :
// extraire le path de l'url
if(!$tUrl        = parse_url($url)) die('url invalide !');
$path            = $tUrl['path'];
// extraire l'extension du path
$tPath            = pathinfo($path);
$extension        = strtolower($tPath['extension']);

//
// selon l'extension, construire la chaine $strInsert que l'on va insérer dans le HTML
$strInsert         = null;
switch ($extension) {
default:
die('extension non traitée !');
break;
case 'js':
$strInsert    = "\n".'<script language="javascript" type="text/javascript" src="'.$url.'"></script>';
break;
case 'css':
//$strInsert    = "\n".'<style type="text/css" media="print">@import url('.$url.');</style>';
$strInsert    = "\n".'<link rel="stylesheet" type="text/css" href="'.$url.'" />';
break;
}

//
// s'il n'y a rien à insérer, inutile d'aller plus loin
if(is_null($strInsert) || empty($strInsert)) return false;

//
// chercher dans le buffer de sortie la balise </head
// on va ajouter juste avant notre appel de fichier
$content        = ob_get_contents();
$regex            = "/\<\/head\>/si";
$content        = preg_replace($regex, $strInsert."\n".'</head>', $content);

//
// vider le contenu du buffer pour ajouter celui qu'on vient de modifier (variable $content);
ob_clean();
echo $content;

//
// ajouter le fichier à la liste des fichiers déjà ajoutés au code
self::addFileUsed    ($url);

//
// confirmer que tout s'est bien passé
return true;
}

III-F - Conclusion sur la classe principale

Nous voilà donc avec une Classe fonctionnelle et générique, qui devrait nous permettre d'insérer n'importe quelle sorte de scripts javascript dans nos pages, facilement et rapidement, ce à n'importe quel endroit de notre code (du moment que les balises head ont été envoyées).

Vous pouvez retrouver la Classe complète en dernière page de ce tutoriel.

Examinons maintenant ce que cette classe peut nous permettre de faire.

IV - Cas pratique : une miniature HighSlide

IV-A - Introduction

HighSlide est un mini framework JavaScript pour générer des images zoomables, des galeries photo, des textwrappers…

HighSlide est payant, mais gratuit pour un usage personnel.

Nous allons créer une classe JS_Generate_Highslide pour générer des miniatures, classe fille de notre classe JS_Generate.

IV-B - La Classe JS_Generate_Highslide

Créons le fichier JS_Generate_Highslide.php qui va contenir notre classe.

Téléchargez la dernière version de HighSlide. Décompressez l'archive et placez-la dans le dossier js/highslide.

class JS_Generate_Highslide extends JS_Generate {

/*
* METHODES
* ____________________________________________ */

/**
* constructeur : on va ajouter un script qui va instancier nos scripts
* @param    string    répertoire de highslide
* ------------------------------------------------------
*/
public function __construct($hsDir    = null) {
//
// définir le répertoire de Highslide
if(!is_null($hsDir) && !empty($hsDir) && is_string($hsDir)) {
//
// "/" final
if(!substr($hsDir, strlen($hsDir) - 1, 1) != '/') {
$hsDir    .= '/';
}
$this->setDir($hsDir);
}

//
// initialiser highslide
$this->init();
}
}

Arrêtons-nous sur ce code assez simple.

Le constructeur de la classe (qui hérite, rappelons-le, de JS_Generate) peut attendre un paramètre : le dossier qui contient HighSlide.

La méthode init sera détaillée plus bas.

Pour l'instancier, nous allons modifier notre fichier index.php

//
// démarrer la temporisation du flux de sortie
ob_start();

//
// fichier requis
require_once 'class/JS_Generate.php';
require_once 'class/JS_Generate_Highslide.php';

//
// notre page simplifiée (sans doctype, etc.)
echo '<html><head></head><body>';

//
// instance d'un objet highslide
$oHighslide        = new JS_Generate_Highslide('js/highslide');

IV-C - Installer HighSlide sur une page HTML

Nous allons maintenant créer une méthode qui va installer highslide sur nos pages web, comme expliqué dans la documentation officielle.

Pour cela, créons la méthode init qui va écrire les scripts d'appel.

/**
* initialiser highslide : appeler les fichiers, etc.
* ------------------------------------------------------
*/
public function init() {
//
// si on n'a pas encore utilisé highslide sur la page
$alreadyUser        = $this->isAlreadyUsed();
if(!$alreadyUser) {

//
// ajouter les fichiers nécessaires au fonctionnement des scripts
$hsDir    = $this->getDir();
$this->addFile($hsDir.'highslide-full.js');
$this->addFile($hsDir.'highslide.css');
$this->addFile($hsDir.'highslide-ie6.css');

//
// initialiser highslide
$strInit    = "hs.graphicsDir = '".$hsDir."graphics/';"
. "hs.outlineType = 'rounded-white';";
$this->addScript($strInit);

//
// signaler que les scripts de démarrage sont écrits
$this->isAlreadyUsed(true);
}
}

IV-D - Une miniature en une ligne de code

Eh oui, c'est désormais presque possible. Il ne nous reste plus qu'à créer une méthode addThumbnail à notre classe, telle que :

/**
* ajouter une miniature
* @param    array données de la miniature / juste le chemin
* ------------------------------------------------------
*/
public function addThumbnail($tImage) {

//
// si $tImage n'est pas un tableau, alors utiliser $tImage comme l'url  à utiliser
if(is_string($tImage)) $tImage = array('src' => $tImage);

//
// id par défaut
if(!isset($tImage['id'])) {
$id        = $this->getId();
$id++;
$this->setId($id);
$id     = 'hs_thumb'.$id;
}

//
// paramètres de la miniature
$src        = $tImage['src'];
$height        = isset($tImage['height'])    ?  $tImage['height']     : '80';
$width        = isset($tImage['width'])    ?  $tImage['width']     : '80';
$alt        = isset($tImage['alt'])        ?  $tImage['alt']         : 'miniature';
$hsStyle    = 'rounded-white';

//
// construire la miniature
$strThumb    = "\n".'<a id="'.$id.'" href="'.$src.'" class="highslide" onclick="return hs.expand(this,'
. "{outlineType: '".$hsStyle.'\'})">'
. "\n".'<img src="'.$src.'" alt="'.$alt.'"'
. 'title="Agrandir" height="'.$height.'" width="'.$width.'" /></a>'
. '';

//
// ajouter au code
$this->addContent($strThumb);

//
// retour
return true;
}

Et maintenant, tout simplement dans le fichier index.php :

//
// ajouter une miniature
$oHighslide->addThumbnail('monimage.jpg');

Vous avez désormais une image zoomable sur votre site. N'hésitez pas à cliquer dessus…

V - Conclusion et fichiers

V-A - Conclusion

Nous avons donc utilisé les fonctions de bufferisation pour ajouter des scripts et appels de fichiers aux pages web.

Une fois la Bufferisation temporisée (ob_start()), il est possible de récupérer le contenu du buffer (ob_get_contents()), de le vider (ob_clean()), etc. Il n'est pas possible d'agir directement sur le flux (de le modifier directement), mais on peut effectuer une action équivalente en le récupérant et le vidant (ob_get_clean()) puis en renvoyant au navigateur client le flux modifié (echo).

La génération de JavaScript est un exemple parmi d'autres, et la Classe de ce tutoriel fait plus figure d'illustration que de composant définitif à insérer dans vos sites. Toutefois, il est tout à fait possible de s'en inspirer, voire de la compléter de manière à obtenir une solution efficace, pour toutes sortes de scripts et frameworks JavaScript.

Il est à noter cependant que cette méthode est, en toute logique, inutile dans un environnement de travail totalement Orienté Objet, dans lequel aucun code HTML n'est écrit « en dur ». Toutefois, j'espère que cet exemple vous aura permis de comprendre ce que sont les fonctions de Gestion de la Bufferisation et ce qu'elles peuvent apporter.

V-B - Fichiers et ressources

Vous pouvez récupérer la source complète de ce tutoriel en suivant ce lien.

HighSlide est disponible sur www.highslide.com, ainsi qu'une documentation détaillée et des exemples.

V-C - Remerciements

Encore une fois, un grand merci à Yogui, responsable bénévole de la rubrique PHP, pour son investissement vraiment important et précieux, tant pour ses conseils que pour sa relecture.