La problématique est la suivante: on n'utilise qu'un seul fichier de template pour plusieurs fichiers de données. Les URLs sont par défaut du type "template.php?page=toto.html". On souhaiterait que les URLs soient indépendantes de notre système de template vis à vis de l'utilisateur, en gros que l'utilisateur qui veut accéder aux données de toto.html demande bien toto.html et non template.php?page=toto.html.
Sous Apache, l'utilisation du mod_rewrite de PHP permet de rajouter la couche d'abstraction qui nous manque. Via un fichier htaccess, Apache (branches 1.* ou 2.*) nous permet de traduire la page demandée par l'utilisateur en une page qui nous arrange bien, et permet même pour cela l'utilisation d'expressions régulières. Pour de plus amples informations sur le mod_rewrite vous pouvez consulter la page Apache consacrée à ce sujet. Le mod_rewrite permet beaucoup de choses, nous n'en utiliserons qu'une infime partie ici.
Voilà donc à quoi va ressembler notre fichier .htaccess que nous mettrons à la racine de notre site web. Il s'agit du code simplifié d'un de mes sites web.
# Réécriture d'url pour les fichiers .htm ou .html
# Le QSA permet de conserver les variables passées en GET
RewriteEngine On
RewriteRule (.*)\.htm(l?)$ /templates/xhtml.tpl.php?page=$1\.htm$2&type=main [QSA]
# Dans les rewritecond, liste des sites à ne pas interpréter. En l'occurence, les
# fichiers système (arbitrairement, .tpl.php)
RewriteCond %{REQUEST_FILENAME} !.tpl.php
RewriteRule (.*)\.php$ /templates/xhtml.tpl.php?page=$1\.php&type=main [QSA] # On redirige les .php
On peut distinguer deux systèmes différents dans la génération des données résultat par traitement xslt. En effet, nos fichiers sources peuvent très bien être des fichiers contenant du code PHP. On a donc le choix entre interpréter ce code PHP ou non. La non-interprétation du code PHP n'est utile que dans le cas de la mise en cache de la page, que nous verrons au chapitre suivant. Nous allons donc voir ici comment à partir d'une page PHP sans présentation, nous pouvons arriver à notre page résultat, dans ce cas c'est plutot simple. Voilà le code que j'utilise pour l'instant.
<?php
$xsl = array('main' => 'common.xsl');
ob_start();
ob_clean();
require '../'.$page;
$content = ob_get_contents();
ob_end_clean();
$content = utf8_encode($content);
$xh = new xslt($content, $_SERVER["DOCUMENT_ROOT"].'/xslt/'.$xsl[$_GET['type']],TRUE, 'sablot');
$xh->transform();
echo '<?xml version="1.0" encoding="ISO-8859-1"?>'."\n";
echo $xh->output();
?>
Les fonctions ob_* permettent de rediriger la sortie de PHP vers une variable; ainsi, avec notre require, on inclue tout simplement la page de données, on l'exécute, et on met le résultat dans $content. On encode le résultat en utf_8 (problème de charset, faudra que je (re)regarde ca de plus près). La suite est basée sur la classe XSLT d'Olivier meunier (Neokraft - Télécharger, Documentation), et fournit une abstraction vis à vis du moteur XSLT. En l'occurence, d'après le rewrite ci-dessus, on assigne le type "main" aux fichiers php et html, on va donc utiliser le fichier 'common.xsl' pour transformer le fichier php.
J'ai uploadé et commenté un fichier xslt simple. J'espère que les commentaires de la source suffisent, je rajouterait un peu de barratin ici un autre jour. Demain. Sisi.
Le XSLT, c'est bien, ca permet plein, plein de trucs, mais c'est lourd. Puis il n'est pas nécessaire de refaire tout le traitement xslt à chaque requête. On va donc mémoriser le résultat du traitement du xslt, et le mettre dans une autre page qu'on écrira sur le disque. Ainsi, lorsqu'un utilisateur fera une requête, on examinera si la page est en cache, et si oui, si elle a besoin d'être regénérée. En pratique, on va refaire le traitement xslt si:
Dans tous les autres cas, on se contentera de rediriger sur la page mise en cache, ce qui apporte un gain de performance important. En pratique, ca se résume très simplement par une petite ruse dans le fichier xsl, et par la suppression de la section des ob_*, remplacée par une lecture des plus basiques. Allez, je vous donne le code brut, il est pas très différent de celui avec interprétation du PHP:
<?php
// J'ai rajouté quelques césures de ligne pour la mise en page
if (!file_exists('../cache/'.$page) ||
filemtime($_SERVER["DOCUMENT_ROOT"].'/xslt/'.$xsl[$_GET['type']]) >
filemtime('../cache/'.$page) || filemtime('../'.$page) >
filemtime('../cache/'.$page)) {
// Ce code s'exécute si la transformation XSLT doit être refaite.
// Lecture du fichier de données
$fp = fopen('../'.$page, 'r');
$content = fread($fp, filesize('../'.$page));
fclose($fp);
$content = utf8_encode($content);
$xh = new xslt($content, $_SERVER["DOCUMENT_ROOT"].'/xslt/'. $xsl[$_GET['type']],
TRUE, 'sablot');
$xh->transform();
// Ecriture dans le fichier de cache
if (dirname($page) == '.' && !file_exists('../cache/'))
mkdir('../cache/');
else {
if (!file_exists('../cache/'.dirname($page)))
mkdir('../cache/'.dirname($page));
}
$fp = fopen('../cache/'.$page, 'w+');
fwrite($fp, '<?php echo \'<?xml version="1.0" encoding="ISO-8859-1"?>\'; ?>'."\n");
fwrite($fp, $xh->output());
fclose($fp);
}
else
require '../cache/'.$page;
?>