Comparatif : Découverte des principaux moteurs de template en PHP

Smarty, phpBB2, TinyButStrong, VTemplate, ModeliXe
Image non disponible


précédentsommairesuivant

V. TinyButStrong

V-A. Présentation

Image non disponible

Le projet

TinyButStrongLe site officiel de TinyButStrong

L'idée avec TinyButStrong est de réutiliser autant que possible tout le code existant, que ce soit du PHP ou du HTML.

L'auteur, Skrol29, est quelqu'un de très ouvert et toujours disponible pour les utilisateurs de TBS. Je ne saurai trop le remercier pour sa patience après sa lecture de la première version de ce comparatif. C'est toujours un plaisir de voir qu'une communauté conserve son personnage moteur.

Skrol29 a minutieusement révisé toute la partie "TinyButStrong" de ce comparatif. J'ai conservé certains de mes commentaires mais le côté technique est corrigé. Il ne subsiste pas d'erreurs.

Installation

Extraire le script tbs_class_php5.php comme vous en avez maintenant l'habitude (hors de la racine du serveur Web).

Arborescence du projet :
  • C:\Web\offline\shared\TBS\ : La bibliothèque (tbs_class_php5.php) ;
  • C:\Web\offline\shared\TBS\plug-ins\ : Le plugin de cache (tbs_plugin_cache.php) ;
  • C:\Web\offline\comparatifs\tbs\ : La classe personnalisée (MyTinyButStrong.php) ;
  • C:\Web\online\comparatifs\ : Les scripts à charger par le navigateur (tbs-nocache.php et tbs-cache.php).

Héritage de la classe

Voici la classe que je vous propose d'utiliser :

MyTinyButStrong.php
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
<?php

require('C:/Web/offline/shared/TBS/tbs_class_php5.php');

class MyTinyButStrong extends clsTinyButStrong
{
    public $Caching;

    public $_BasePath;
    public $_PluginsPath;
    public $_TemplatePath;
    public $_CacheLifeTime;

    public function __construct($template, $caching)
    {
        parent::__construct();

        //
        // Configuration de l'objet
        //
        $this->NoErr = TRUE;
        $this->Caching = $caching;
        $this->HtmlCharSet = 'ISO-8859-1';
        $this->_ChrOpen = '{$';
        $this->_ChrClose = '}';
        $this->_ChrVal = '{$val}';

        $this->_BasePath = dirname(__FILE__);
        $this->_PluginsPath = 'C:/Web/offline/shared/TBS/plug-ins/';
        $this->_TemplatePath = $this->_BasePath.'/templates/'.$template.'/html/';

        if($this->Caching)
        {
            $this->_CachePath = $this->_BasePath.'/templates/'.$template.'/cache/';
            $this->_CacheLifeTime = 180; // En secondes

            //
            // Installation et démarrage du plugin de cache
            //
            require_once($this->_PluginsPath.'tbs_plugin_cache.php');
            $this->PlugIn(TBS_INSTALL, TBS_CACHE, $this->_CachePath, '*.cache');
        }
        else
        {
            $this->Render = TBS_OUTPUT; // TBS_NOTHING ou TBS_OUTPUT ou TBS_EXIT
            $this->_CachePath = NULL;
            $this->_CacheLifeTime = NULL; // En secondes
        }
    }

    public function LoadTemplate($file, $merge = FALSE)
    {
        if($merge=='+')
        {
            parent::LoadTemplate($this->_TemplatePath.'/'.$file, '+');
        }
        else
        {
            if($this->Caching)
            {
                $this->PlugIn(TBS_CACHE, $file, $this->_CacheLifeTime);
            }

            parent::LoadTemplate($this->_TemplatePath.'/'.$file, $this->HtmlCharSet);
        }
    }
}

?>

Le code suivant permet de créer l'objet dans nos scripts :

tbs-nocache.php
Sélectionnez
define('DIR_OFFLINE', 'C:/Web/offline/comparatifs/tbs/');
define('TBS_TEMPLATE', 'default');
define('TBS_CACHING', FALSE);

require(DIR_OFFLINE.'MyTinyButStrong.php');

$template = new MyTinyButStrong(TBS_TEMPLATE, TBS_CACHING);

//
// Série d'appels à $temlate->LoadTemplate() suivis d'envoi de données au gabarit
//

$template->Show();

Le constructeur attend le nom du fichier de gabarit et un booléen déterminant si l'on souhaite utiliser le cache.

Notez que j'ajoute une propriété $_TemplatePath à la classe. Je la laisse en accès public par souci d'homogénéité avec la classe de départ mais, si cela n'avait tenu qu'à moi, elle aurait été protected.

J'ai également apporté une amélioration afin d'éviter de devoir répéter le chemin d'accès au gabarit à chaque chargement de fichier de gabarit.

En temps normal, la méthode LoadTemplate() s'adapte automatiquement : la première utilisation définit le répertoire des gabarits, puis les utilisations suivantes peuvent se contenter de donner le nom du fichier de gabarit sans évoquer le chemin d'accès complet.

Je pars ici du principe que seul le premier appel à LoadTemplate() construit le gabarit à partir de rien, tous les autres étant a priori pour y ajouter du code HTML, c'est pourquoi ces appels ont le paramètre '+' lorsque la propriété Source est non vide.

Enfin, TBS permet de configurer les marqueurs de début et de fin de bloc. J'ai pris le parti de les changer. Par défaut, TBS utilise des crochets [], or je commence à m'habituer aux accolades et au dollar {$} des autres moteurs, j'ai donc utilisé ces caractères.

TBS peut configurer ces marqueurs de plusieurs manières. J'ai adopté ici la méthode la plus explicite, mais le constructeur peut le faire également.

V-B. Utilisation

Variables

TBS sait lier automatiquement les variables locales PHP avec le gabarit. Pour cela, il faut que les variables du gabarit respectent une syntaxe spéciale : {$var.nom_de_la_variable}.

Cependant, cela demanderait des manipulations peu pratiques et peu explicites. Il est préférable d'utiliser la méthode MergeField(), dont les intentions sont limpides.

Syntaxe générique :
Sélectionnez
$template->MergeField('variable_du_gabarit', 'valeur_php');
header.php
Sélectionnez
$result = mysql_query('SELECT name, value FROM header') or die(mysql_error());
while($meta = mysql_fetch_assoc($result))
{
    $template->MergeField('meta_'.$meta['name'], $meta['value']);
}
header.tpl
Sélectionnez
    <meta name="generator" content="{$meta_generator}" />
    <meta name="description" content="{$meta_description}" />
    <meta name="keywords" content="{$meta_keywords}" />
    <meta name="MS.LOCALE" content="{$meta_mslocale}" />

Le comportement par défaut de TBS est de convertir les valeurs en entités HTML à l'aide de la fonction htmlspecialchars().

Blocs

TinyButStrong a une manière très particulière de gérer les répétitions de portions de gabarit, qu'il appelle "blocks". La méthode MergeBlock() s'adapte en fonction des paramètres qui lui sont passés, par exemple ici une ressource MySQL ou un tableau de valeurs :

home.php (utilisant un tableau PHP)
Sélectionnez
$subjects = array();
$result = mysql_query($sql['subjects']);
while($subject = mysql_fetch_assoc($result))
{
    $sujects[] = $subject;
}
$template->MergeBlock('subject', $subjects);
home.php (utilisant une requête SQL)
Sélectionnez
$template->MergeBlock('subject', $resource, 'SELECT id, title FROM subject')
home.tpl
Sélectionnez
{$subject;block=begin}
    <div class="bloc_cours">
        <div class="titre_cours">{$subject.title}</div>
    </div>
    <br />
    <br />
{$subject;block=end}

Dans cet exemple, TBS fusionne les données directement depuis la requête SQL. Il faut bien se rappeler que, selon les bonnes règles de design d'une application, les requêtes SQL devraient retourner des informations prêtes à être affichées (hors conversion vers le charset de destination). Il ne devrait donc pas être nécessaire de récupérer les données avant de les envoyer au gabarit.

Imbriquation

home.php (utilisant des requêtes SQL)
Sélectionnez
$sql = 'SELECT id, title
        FROM subject';
if($template->MergeBlock('subject', $resource, $sql) > 0)
{
    $sql = 'SELECT id, title
            FROM category
            WHERE subject_id = %p1%';
    if($template->MergeBlock('category', $resource, $sql) > 0)
    {
        $sql = 'SELECT id, uri, title, description
                FROM tutorial
                WHERE category_id = %p2%';
        $template->MergeBlock('tutorial', $resource, $sql);
    }
}
home.php (utilisant un tableau hiérarchique PHP)
Sélectionnez
if($template->MergeBlock('subject', 'array', 'subjects') > 0)
{
    if($template->MergeBlock('category', 'array', 'subjects[%p1%][categories]') > 0)
    {
        $template->MergeBlock('tutorial', 'array', 'subjects[%p1%][categories][%p2%][tutorials]');
    }
}
home.tpl
Sélectionnez
{$subject;block=begin}
    <div class="bloc_cours">
        <div class="titre_cours">{$subject.title}</div>
    {$category;block=begin;p1={$subject.id}}
        <div class="categorie_cours">{$category.title}</div>
        <div class="liste_cours">
            <ul>
        {$tutorial;block=begin;p1={$subject.id};p2={$category.id}}
                <li>
                    <a href="{$tutorial.uri}">{$tutorial.title}</a> : {$tutorial.description}
                </li>
        {$tutorial;block=end}
            </ul>
        </div>
        <hr />
    {$category;block=end}
    </div>
    <br />
    <br />
{$subject;block=end}

TBS utilise des variables automatiques dans le code PHP (p1, p2 p3, etc.) pour relier les blocs imbriqués. Cette variable est remplacée à chaque itération : on utilise habituellement une variable issue du bloc parent.

Le fichier de gabarit construit cette variable dans la définition du block, tandis que le script PHP l'utilise pour filtrer les données.

Le secret de l'imbrication des boucles repose dans ces variables automatiques.

NB : Le dernier extrait de code PHP correspond au tableau hiérarchique construit lors de l'utilisation du moteur Smarty, dans le script home.php.

Alternance

Il suffit de répéter le block dans le gabarit, ce qui oblige TBS à utiliser une alternativement tous les blocks.

Le gabarit est alors dans l'esprit de ce que nous connaissons :
Sélectionnez
{$subject;block=begin}
    <div class="bloc_cours">
        <div class="titre_cours">{$subject.title}</div>
    {$category;block=begin;p1={$subject.$}}
        <div class="categorie_cours">{$category.title}</div>
        <div class="liste_cours">
            <ul>
        {$tutorial;block=begin;p1={$subject.$};p2={$category.$}}
                <li>
                    <a href="{$tutorial.uri}">{$tutorial.title}</a> :
                    <span style="color: green;">{$tutorial.description}</span>
                </li>
        {$tutorial;block=end}
        {$tutorial;block=begin}
                <li>
                    <a href="{$tutorial.uri}">{$tutorial.title}</a> :
                    <span style="color: blue;">{$tutorial.description}</span>
                </li>
        {$tutorial;block=end}
            </ul>
        </div>
        <hr />
    {$category;block=end}
    </div>
    <br />
    <br />
{$subject;block=end}

Segmentation du gabarit

Comme très souvent avec TinyButStrong, il y a plusieurs manières de procéder.

L'approche décrite dans la documentation (dans la section subtemplates) permet d'avoir une gestion similaire à celle proposée par Smarty : le gabarit principal (home.tpl) définit les gabarits à inclure lors de son chargement par l'objet TBS. Dans approche, TinyButStrong permet d'exclure ce qui se trouve à l'extérieur de la balise <body> (configurable avec getbody=balise), ce qui est utile lorsque l'on utilise des sous-gabarits construits par un outil comme Dreamweaver.

Une autre approche consiste à charger les gabarits les uns après les autres par la méthode LoadTemplate() :

tbs*.php
Sélectionnez
$template = new MyTinyButStrong(TBS_TEMPLATE, TBS_CACHING);

// Chargement des différents gabarits dans le moteur
$template->LoadTemplate('header.tpl');
$template->LoadTemplate('home.tpl', '+');
$template->LoadTemplate('footer.tpl', '+');

//
// Mettre ici l'envoi des données au moteur
//

// Affichage de la page finale
$template->Show();

Gestion du cache

TBS dispose d'un plugin (CacheSystem, inclus en fourniture standard) permettant de gérer la mise en cache du document produit.

Inclusion :
Sélectionnez
require_once('tbs_plugin_cache.php');
Configuration :
Sélectionnez
$template->PlugIn(TBS_INSTALL, TBS_CACHE, $template>_CachePath, '*.cache');
Utilisation :
Sélectionnez
$template->PlugIn(TBS_CACHE, 'file.tpl, $template->_CacheLifeTime);

Dans la mesure où TinyButStrong se charge des requêtes à la base de données, il n'est pas nécessaire de vérifier la validité du cache comme avec les autres moteurs de gabarit. En effet, TBS n'effectue pas de requête s'il a déjà les résultats en cache.

V-C. Mon avis

Avantages

PHP 4 et 5.

Possibilité d'installer et de développer des plugins.

La syntaxe "relative" du gabarit peut sembler déroutante, mais en réalité elle est ingénieuse : elle permet de créer des gabarits XML depuis l'intérieur d'une application comme Office2007 ou OpenOffice. D'ailleurs, un plugin OpenOffice existe pour TBS...

TBS est prévu pour utiliser des gabarits 100% compatibles W3C, ce qui permet à un graphiste de créer les documents avec son outil favori.

Inconvénients

Cela relève du commentaire personnel mais je n'adhère pas toujours à la manière de coder, notamment l'utilisation de constantes dans le scope global plutôt qu'à l'intérieur d'une classe (groupement de classes dans un même script, un même paramètre ou une même méthode peut avoir diverses utilisations).

Construire des requêtes SQL n'est pas la vocation de TBS (et j'adhère à ce refus) mais la classe permet néanmoins d'exécuter des requêtes. Cependant, aucune protection n'est appliquée aux requêtes SQL envoyées par MergeBlock() ; il appartient au développeur de le faire avec la bonne fonction : par exemple pour MySQL, il s'agit de mysql_real_escape_string().

Conclusion

TBS est le moteur de gabarits qu'il m'a été le plus difficile de maîtriser. J'ai finalement réussi à atteindre mon objectif (reproduire le document initial) : je le dois à l'aide patiente de son auteur, Skrol29.

Un résumé est disponible en fin d'article.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2006 Guillaume Rossolini. 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.