Mis à jour le jeudi 31 octobre 2013
  • Facile
Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Introduction du cours

La plupart des sites Web n'ont pas l'ambition, ni l'utilité, de faire usage d'un système de templates classique. Pour la plupart d'entre eux, au contraire, structure et contenu peuvent cohabiter sans problème.

Dans ce cas, en quoi un système simplifié de gestion de contenu peut se justifier ?

Imaginons que vous envisagiez, dans un avenir plus ou moins proche, de traduire votre site (en anglais, par exemple) : avec le système simplifié que nous allons étudier, nul besoin de reprendre une par une les différentes pages ; il vous suffit de traduire le seul fichier renfermant les contenus textuels. Autre hypothèse, vous décidez de confier le développement du script à un collègue, tandis que vous prenez en charge le contenu du projet ; dans ce cas, vous n'avez qu'à vous focaliser sur le fichier du contenu textuel.

D'une manière plus globale, la méthode exposée ci-après présente tous les avantages d'un développement propre, séparant structure et contenu. Commençons donc sans plus tarder !

Structure générale du système de contenu

Ce tutoriel sur les includes peut alors vous intéresser.
Si toutefois vous ne pouvez ni ne voulez utiliser ce système, une solution existe, mais elle est plus lourde à mettre en œuvre. Je la traiterai également.

Bien. Voyons à présent l'idée générale de notre méthode.

Prérequis

Sur les acquis théoriques

Ce tutoriel ne fait pas appel à de bouleversantes nouveautés, mais il combine bon nombre de méthodes relativement avancées. Pour cette raison, il est bon d'avoir lu le tutoriel de M@teo21 sur le PHP (un incontournable, me direz-vous), et d'être familier de la documentation PHP vers laquelle je renverrai un certain nombre de fois.

Sur la structure de votre site

Outre la remarque précédente, je suppose que nous avons à disposition un fichier configuration.php, un fichier index.php, un fichier lang.php, un fichier en-tete.php et un fichier pied.php dans le même répertoire, pour simplifier.

Hébergement Web

Cette méthode a été testée et appliquée avec succès chez Free. Si vous rencontrez une incompatibilité avec d'autres hébergeurs, n'hésitez pas à le signaler dans vos commentaires !

Idée générale

La plupart des fichiers PHP mêlent le code et le contenu. On écrira ainsi par exemple :

<?php
$reponse = 42;
$proposition = $_GET['proposition'];

if ($reponse == $proposition)
{
	echo '<p>Félicitations ! Vous avez trouvé la réponse (42) !</p>';
}
else
{
	echo '<p>Retentez votre chance !</p>';
}
?>

J'en conviens, l'intérêt est ici limité, mais on voit l'idée (j'espère).

Eh bien le but de ce tutoriel sera d'arriver à un code qui ressemble à ceci :

<?php
$reponse = 42;
$proposition = $_GET['proposition'];

if ($reponse == $proposition)
{
	echo '<p>{bonne_reponse}</p>';
}
else
{
	echo '<p>{mauvaise_reponse}</p>';
}
?>

La différence entre ce dernier extrait de code et l'extrait précédent repose sur la forme du contenu. Ici, l'on a remplacé « Félicitations ! Vous avez trouvé la réponse (42) ! » par « {bonne_reponse} », et « Retentez votre chance ! » par « {mauvaise_reponse} ».

Mais le visiteur ne verra donc que ces textes sans ponctuation et entre accolades ?

Que nenni ! Le visiteur verra le texte que l'on a remplacé. En revanche, ce texte n'apparaît plus dans le code lui-même, puisque nous avons décidé de séparer structure et contenu. Il faudra donc, au moment d'afficher la page, remplacer les accolades par le texte qui correspond (objet de la deuxième partie de ce tutoriel).

Mais où est donc passé ce texte ?

On l'a tout bonnement stocké dans un fichier indépendant où seront également stockés tous les autres textes. C'est justement l'objet du prochain paragraphe…

Le fichier lang.php

Ce fichier a une structure on ne peut plus simple. En effet, son rôle est d'associer à chaque élément entre accolades, un texte mis en forme. De fait, il convient d'utiliser un tableau associatif, ou array().

Reprenons l'exemple précédent : pour afficher les deux messages que nous avons vus, il nous faudra écrire :

<?php
$fr = array(
	'bonne_reponse' => 'Félicitations ! Vous avez trouvé la réponse (42) !',
	'mauvaise_reponse' => 'Retentez votre chance !'
);
?>

Cette portion de code ne nécessitera pas d'explication supplémentaire, je pense ; notons simplement que le tableau est ici stocké dans la variable $fr, car il s'agit là des textes en français. Lorsque nous voudrons traduire notre site (et c'est là qu'apparaît l'intérêt majeur de cette méthode de gestion de contenu), il suffira de créer un deuxième tableau, $en par exemple, puis $de, $es… et de poursuivre ainsi à l'envi, car cette méthode n'est pas limitative.

Certes, mais comment choisir le bon tableau ?

J'y reviendrai plus tard, en troisième partie de ce tutoriel. Pour l'instant, sachez que vous pouvez ajouter une multitude de langues à votre site en toute simplicité, et surtout sans modifier vos pages.

Pour résumer…

  • Tous les textes, messages et de manière générale, tout ce qui est destiné à être lu et compris par vos visiteurs, disparaît de votre code.

  • À la place, on pose des appels entre accolades, qui seront remplacés par le texte mis en forme à l'affichage.

  • Pour procéder à ce remplacement, on stocke tous les contenus dans le fichier lang.php sous la forme d'un tableau associant les appels et le contenu.

Voyons donc à présent comme mettre en œuvre ce remplacement !

Fonctionnement détaillé de la méthode de gestion

Le but de cette partie est de remplacer tous les appels (entre accolades) de vos pages par les contenus textuels qui correspondent. D'ores et déjà, nous disposons d'un tableau de correspondance, stocké dans la variable $fr, dans le fichier lang.php.

Méthode avec includes centralisés

Si vous centralisez vos includes sur index.php, votre fichier doit contenir ces quelques lignes (plus une bonne dose de sécurité, sur laquelle je ne reviendrai pas) :

<?php
$p = (!empty($_GET['p'])) ? htmlentities($_GET['p']) : 'index';
 
$array_pages = array(
	'index' => 'index.php',
	'page1' => 'page1.php',
	'page2' => 'page2.html'
);

if(!array_key_exists($p, $array_pages)) $page = 'index.php';
elseif(!is_file($array_pages[$p])) $page = 'erreur.php';
else $page = $array_pages[$p];


include('configuration.php'); // Insertion du fichier de configuration
include('en-tete.php'); // Insertion de l'en-tête commun à toutes les pages
include($page); // Insertion de la page requise
include('pied.php'); // Insertion du pied de page commun à toutes les pages
?>

Ce code est une reprise adaptée du tutoriel de Quadehar que je vous ai conseillé plus haut.

Pour continuer, nous allons devoir faire un tour dans le fichier configuration.php contenant les fonctions du site.

Le fichier de configuration

Le fichier de configuration contient au moins deux fonctions nécessaires à notre système : la première, que j'appelle disp_txt() ? pour display text ?, a un argument (a) ; elle appelle le fichier lang.php et retourne le texte associé à l'argument (a est une clef du tableau).

La seconde, make_txt(), a pour argument une chaîne c, et renvoie cette même chaîne dans laquelle elle a préalablement remplacé les appels à du texte (entre accolades) par ce texte. Je sens que vous voyez venir la solution…

Voici ces deux fonctions, dans leur ordre obligatoire d'apparition :

<?php
function disp_txt($txt)
{
	include('lang.php');
	// On inclut le fichier lang.php dans lequel est définie la variable tableau $fr.
	
	$lang = $fr;
	
	if (array_key_exists($txt, $lang)) // Si le tableau a l'entrée demandée…
	{
		$return = $lang[$txt]; // … on retourne cette entrée.
	}
	else
	{
		$return = '(Texte manquant)'; // Sinon, on retourne « (Texte manquant) » (par exemple).
	}
	
	return ($return);
}
function make_txt($page)
{
	$page = preg_replace('#\{([a-z0-9_]*)\}#e', 'disp_txt($1)', $page);
	
	return $page;
}
?>

Je crois bon de m'étendre un peu sur ces deux fonctions.

Pour la première, disp($txt), je pense qu'un exemple suffira : disp('bonne_reponse') affichera « Félicitations ! Vous avez trouvé la réponse (42) ! », en allant, dans la variable-tableau $fr, chercher le texte correspondant à son argument (ici, 'bonne_reponse').

La seconde utilise une expression régulière (ou regex, de l'anglais regular expression) dans la fonction de capture et remplacement preg_replace(). Quel est l'effet de cette fonction ? Elle cherche, dans la chaîne d'entrée, des appels signalés par des accolades (par exemple {bonne_reponse}), retire ces accolades et passe le résultat à la fonction disp() : on obtient disp('bonne_reponse').

Mais la regex n'ajoute pas les apostrophes autour de bonne_reponse ! D'où viennent-elles ?

C'est exact, mais les captures opérées grâce à preg_replace() renvoient des chaînes de caractères (ici, $1). Si on les réinjecte dans une fonction comme fait ici, elles se comportent comme telles.

Mais, mais, quelle est cette option « e » inconnue ?

Excellente question ! C'est le nœud du problème. En effet, la fonction preg_replace(), utilisée sans option, ne permet pas d'insérer une fonction au deuxième argument. Cette difficulté est contournable grâce à l'option « e », dont l'effet est de faire interpréter le contenu du deuxième argument (censé être une chaîne) comme du PHP : ainsi, je peux y insérer la fonction disp() qui sera interprétée. Tout bonnement !

Mise en pratique

Voici une mise en pratique (simple, pour commencer) de ces deux fonctions (dont on ne voit que la seconde, la première travaillant en arrière-plan). Supposons qu'une variable $page contienne un bout de code XHTML et un appel entre accolades, et que l'on veuille remplacer cet appel par le texte correspondant. Voici la solution :

<?php
$page = '<p style="color: green;">{bonne_reponse}</p>';
echo make_txt($page);
?>

Ce code retournera cette portion de XHTML :

<p style="color: green;">Félicitations ! Vous avez trouvé la réponse (42) !</p>

Fort bien ! Mais à présent, comment remplacer tous les appels dans toutes nos pages ? En les faisant rentrer dans des variables $page ?

Que nenni ! Mais encore une fois, la question est pertinente. Voyons donc l'application en conditions réelles…

Application en conditions réelles

Dans cette partie, j'ai supposé que votre site utilisait un système d'includes centralisés dans index.php. C'est ici que cette précision prend toute son importance. Rappelons en effet la portion de code de index.php que j'ai exposée plus haut :

<?php
$p = (!empty($_GET['p'])) ? htmlentities($_GET['p']) : 'index';
 
$array_pages = array(
	'index' => 'index.php',
	'page1' => 'page1.php',
	'page2' => 'page2.html'
);

if(!array_key_exists($p, $array_pages)) $page = 'index.php';
elseif(!is_file($array_pages[$p])) $page = 'erreur.php';
else $page = $array_pages[$p];


include('configuration.php'); // Insertion du fichier de configuration
include('en-tete.php'); // Insertion de l'en-tête commun à toutes les pages
include($page); // Insertion de la page requise
include('pied.php'); // Insertion du pied de page commun à toutes les pages
?>

Intéressons-nous cette fois aux trois derniers includes. Vous remarquez que c'est dans ces trois fichiers inclus que vous aurez des appels entre accolades (dans l'en-tête, dans la page centrale, et dans le pied de page). C'est donc à ces trois fichiers qu'il faut appliquer la fonction make_txt() afin de remplacer les appels par le texte correspondant. Des idées ?

Eh bien nous allons temporiser la sortie, lui appliquer la fonction make_txt(), puis renvoyer les données !

Je vois d'ici vos yeux ébahis ; mais non, ce n'est pas compliqué, nous avons seulement besoin de deux fonctions : ob_start() et ob_end_flush(). La première stoppe l'envoi d'informations vers le client, et la deuxième met fin à la rétention. En soi, l'intérêt peut sembler limité, mais c'est sans compter que ob_start() peut prendre en argument… une tierce fonction !

Mais un petit bout de code vaut sans doute mieux qu'un long discours. Voici donc, sans plus tarder, la mise en pratique de tout ce qui précède, dans index.php :

<?php
$p = (!empty($_GET['p'])) ? htmlentities($_GET['p']) : 'index';
 
$array_pages = array(
	'index' => 'index.php',
	'page1' => 'page1.php',
	'page2' => 'page2.html'
);

if(!array_key_exists($p, $array_pages)) $page = 'index.php';
elseif(!is_file($array_pages[$p])) $page = 'erreur.php';
else $page = $array_pages[$p];


include('configuration.php'); // Insertion du fichier de configuration

ob_start('make_txt'); // On temporise et on applique la fonction make_txt() à la sortie.

include('en-tete.php'); // Insertion de l'en-tête commun à toutes les pages
include($page); // Insertion de la page requise
include('pied.php'); // Insertion du pied de page commun à toutes les pages

ob_end_flush(); // On met fin à la temporisation et on libère la sortie.
?>

Lisez bien les commentaires que j'ai ajoutés ; ils sont cruciaux pour bien comprendre le comportement de ob_start().

Bon, eh bien nous arrivons enfin au bout de nos peines : dans toutes les pages incluses dans index.php, entre le début et la fin de la temporisation (donc également dans l'en-tête et dans le pied de page), les appels entre accolades seront remplacés par le texte correspondant, que nos fonctions se chargent de chercher dans le tableau de lang.php.

Et si je n'ai pas de système d'includes centralisés ?

Juste remarque ; voyons donc cela.

Méthode sans includes centralisés

Grande nouvelle, la méthode sans includes centralisés est la même que celle avec ! Oui, mais…

Mais voilà. Le principe même des includes centralisés, c'est que l'en-tête, le pied de page et la page centrale ne sont appelés qu'une fois, sur la page index.php. Si l'on ne centralise pas, il faut appeler en-tête et pied de page dans chaque fichier. Par exemple :

<?php
include('configuration.php');
include('en-tete.php');

// Contenu de la page

include('pied.php');
?>

Pourtant, ce code ne vous évoque-t-il rien ? Remontez un peu, vers le contenu du fichier index.php… On s'en rapproche fortement. Eh bien, pour appliquer notre méthode dans le cas présent, il faut temporiser et libérer la sortie, à laquelle on a appliqué la fonction make_txt() sur toutes les pages du site, de cette manière :

<?php
include('configuration.php');

ob_start('make_txt'); // On temporise et on applique la fonction make_txt() à la sortie.

include('en-tete.php');

// Contenu de la page

include('pied.php');

ob_end_flush(); // On met fin à la temporisation et on libère la sortie.
?>

L'inconvénient ? C'est beaucoup plus long puisqu'il faut réitérer dans chaque fichier construit de cette manière. À vous de voir…

Pour aller plus loin...

Cette troisième et dernière partie rappellera la solution adoptée plus haut, puis proposera un prolongement naturel de notre méthode de gestion du contenu : un site multilingue. Enfin, nous y proposerons une méthode avancée de gestion des liens.

Rappel de la solution

Éléments de solution

Cette solution, includes centralisés ou pas, requiert et fait usage :

  • d'un fichier de configuration (configuration.php par exemple) dans lequel sont définies nos deux fonctions disp_txt() et make_txt() ;

  • d'un fichier de langue (lang.php) où sont stockés tous les contenus textuels du site en association avec leurs clés d'appel (celles que l'on retrouve entre accolades dans les pages) ;

  • des fonctions de temporisation ob_start() et ob_end_flush() que l'on utilise soit dans index.php (cas des includes centralisés), soit dans toutes les pages contenant du texte, respectivement avant et après toute sortie textuelle.

Limites diverses

La regex employée dans la fonction make_txt() ? #\{([a-z0-9_]*)\}#e ? limite en soi les possibilités d'appel de texte ; d'une part, ces appels sont entourés d'accolades (moyen de repérage). Ensuite, ces appels ne contiennent que des caractères de bas de casse de a à z, des chiffres et des underscores (_). Il est cependant tout à fait possible de s'affranchir de cette limite (qui laisse déjà le temps de voir venir), en perfectionnant la regex : acceptation des capitales, des tirets, des caractères accentués, etc.

Une autre limite de cette méthode consiste en le monolithisme des textes. Autrement dit, chaque texte forme un bloc, dans lequel il n'est par exemple pas possible d'insérer des liens proprement. Je propose des pistes pour résoudre ce problème au paragraphe « Une méthode avancée de gestion des liens » (infra).

Un site multilingue sans effort...

… ou presque. Vous vous souvenez certainement de ce code, inclus dans lang.php :

<?php
$fr = array(
	'bonne_reponse' => 'Félicitations ! Vous avez trouvé la réponse (42) !',
	'mauvaise_reponse' => 'Retentez votre chance !'
);
?>

La première étape consiste, pour le webmestre ou le traducteur, à copier ce tableau, le coller à la suite en le renommant (par exemple, $en pour l'anglais) et changer les valeurs associées à chaque clef sans changer ces clefs. Cela donnera par exemple :

<?php
$fr = array(
	'bonne_reponse' => 'Félicitations ! Vous avez trouvé la réponse (42) !',
	'mauvaise_reponse' => 'Retentez votre chance !'
);
$en = array(
	'bonne_reponse' => 'Well done! You found the answer! (42)',
	'mauvaise_reponse' => 'Try again!'
);
?>

Bref, voici traduit tout votre site, le tout sans rien changer aux différentes pages. Reste à pouvoir changer de langue au début ou en cours de visite… Reprenons notre fonction disp_txt(), voulez-vous ?

<?php
function disp_txt($txt)
{
	include('lang.php');
	// On inclut le fichier lang.php, dans lequel est définie la variable-tableau $fr.
	
	$lang = $fr;
	
	if (array_key_exists($txt, $lang)) // Si le tableau a l'entrée demandée…
	{
		$return = $lang[$txt]; // … on retourne cette entrée.
	}
	else
	{
		$return = '(Texte manquant)'; // Sinon, on retourne « (Texte manquant) » (par exemple).
	}
	
	return ($return);
}
?>

On y voit notamment « $lang = $fr; » ; c'est là que l'on doit agir pour faire de votre site un polyglotte invétéré. Pour cela, il nous faut connaître la langue de l'utilisateur…

Au fait, d'où vient cette information : la langue du visiteur ?

Cela dépend de la manière dont est conçu votre site Web. Pour la suite, je supposerai que cette information est stockée dans une variable globale $_SESSION['visiteur_langue'], dont la valeur peut être « fr », « en », « de », etc. Bien, retournons à notre fonction. Pour sélectionner le bon tableau à parcourir (c'est-à-dire celui qui correspond à la langue de l'utilisateur), l'on procédera ainsi :

<?php
function disp_txt($txt)
{
	include('lang.php');
	// On inclut le fichier lang.php dans lequel sont définies toutes les variables-tableaux de langue ($fr, $en…).
	
	$lang = $fr; // Par défaut, la langue est le français…
	if (isset($_SESSION['visiteur_langue'])) // … mais s'il existe une information de langue du visiteur, celle-ci prime.
	{
		$visiteur_langue = $_SESSION['visiteur_langue']; // Simplification d'écriture
		$lang = ${$visiteur_langue}; // Variable variable : $lang = $en si l'utilisateur est Anglais, car alors, $visiteur_langue vaut 'en'
	}
	
	if (array_key_exists($txt, $lang)) // Si le tableau a l'entrée demandée…
	{
		$return = $lang[$txt]; // … on retourne cette entrée.
	}
	else
	{
		$return = '(Texte manquant)'; // Sinon, on retourne « (Texte manquant) » (par exemple).
	}
	
	return ($return);
}
?>

Voilà. J'ai le plaisir de vous annoncer que vous êtes désormais à la tête d'un site Web multilingue. Rien de moins.

Une méthode avancée de gestion des liens

Je ne fais ici que donner des pistes que vous pourrez vous amuser à poursuivre, si le cœur vous en dit.

Partons du principe que la méthode de gestion de contenu permet de remplacer l'{appel_au_texte} par « le texte appelé ». Rien n'interdit dès lors de créer de nouvelles fonctions, ou d'enrichir make_txt() afin de remplacer un %appel_au_lien% par un « http://lien-appele.zz/ ». Il suffit pour cela de créer un fichier links.php contenant tous les liens nécessaires au site sous forme d'un tableau associatif (même principe que lang.php), et d'associer les clefs d'appel de liens, entourées (par exemple) de %, aux liens correspondants.

Plus fort : le code XHTML de ces liens pourrait être généré automatiquement grâce à une fonction annexe make_links() associant à une paire clef => valeur, le code <a href="...">{nom_du_lien}</a>. Dès lors, l'on pourrait insérer des liens dans les tableaux du fichier lang.php, passer ces tableaux à la fonction make_links(), puis les traiter dans notre fonction make_txt(). Cela résout le problème de monolithisme des blocs textes.

À vous de trouver la suite !

Nous avons vu que ce système simplifié de gestion de contenu allie souplesse et puissance. En effet, une fois mis en place le module de remplacement, le développement d'applications prend un autre visage puisqu'il sépare clairement contenu et structure. En outre, la gestion multilingue offre un aperçu des capacités d'un tel système dont la portabilité est élevée et l'extensibilité est quasiment infinie.

Exemple de certificat de réussite
Exemple de certificat de réussite