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

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


précédentsommairesuivant

IV. Smarty

IV-A. Présentation

Image non disponible

Le projet

Smarty est un projet du framework PEAR, qui est connu pour sa formidable quantité d'extensions et de classes PHP.

L'idée est de déporter la logique d'affichage depuis le script PHP vers le gabarit. Smarty se charge de tout ce qui se rapproche de près ou de loin de l'affichage du contenu dans la page finale, ce qui inclut certains détails de programmation comme les boucles (par exemple pour parcourir les tableaux).

SmartyLe site officiel de Smarty

Installation

Copiez le répertoire de Smarty dans un répertoire de préférence accessible par tous vos utilisateurs et situé hors de la racine du serveur Web.

Arborescence du projet :
  • C:\Web\offline\shared\Smarty\ : La bibliothèque Smarty et ses dépendances ;
  • C:\Web\offline\comparatifs\smarty\ : La classe personnalisée (MySmarty.php) ;
  • C:\Web\offline\comparatifs\smarty\templates\default\cache : Les fichiers mis en cache par Smarty (CHMOD 0755) ;
  • C:\Web\offline\comparatifs\smarty\templates\default\compiled : Les fichiers compilés par Smarty (CHMOD 0755) ;
  • C:\Web\offline\comparatifs\smarty\templates\default\configuration : La configuration de Smarty (CHMOD 0555) ;
  • C:\Web\offline\comparatifs\smarty\templates\default\html : Les fichiers de gabarit (CHMOD 0555) ;
  • C:\Web\online\comparatifs\ : Les scripts à charger par le navigateur (smarty-nocache.php et smarty-cache.php).

Héritage de la classe

Il est préférable de dériver la classe Smarty pour la personnaliser selon notre projet.

MySmarty.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.
<?php

require('C:/Web/offline/shared/Smarty/Smarty.class.php');

class MySmarty extends Smarty
{
    function MySmarty($template, $caching)
    {
        $basedir = dirname(__FILE__).'/templates/'.$template;

        $this->template_dir = $basedir.'/html';
        $this->cache_dir = $basedir.'/cache';
        $this->compile_dir = $basedir.'/compiled';
        $this->config_dir = $basedir.'/configuration';

        $this->caching = $caching;

        if($this->caching)
        {
            $this->cache_lifetime = 180;
        }
        else
        {
            $this->_CachePath = NULL;
            $this->_CacheLifeTime = NULL; // En secondes
        }
    }
}

?>

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

smarty-nocache.php
Sélectionnez
define('DIR_OFFLINE', 'C:/Web/offline/comparatifs/smarty/');
define('SMARTY_TEMPLATE', 'default');
define('SMARTY_CACHING', FALSE);

require(DIR_OFFLINE.'MySmarty.php');

$template = new MySmarty(SMARTY_TEMPLATE, SMARTY_CACHING);

//
// Envoi de données au gabarit
//

$template->display('home.tpl');

IV-B. Utilisation

Variables

header.php
Sélectionnez
$template->assign('title',
    "Guillaume Rossolini - Cours Web &amp; PHP -"
        ." Club d'entraide des développeurs francophones"
);
header.tpl
Sélectionnez
<title>{$title}</title>

Ce code indique à Smarty qu'il doit remplacer la variable $title du gabarit par la valeur qui lui est assignée (attention aux confusions, $title dans le gabarit n'est pas $title dans le script).

Avec le code suivant, Smarty convertit les chaînes en entités HTML (notez comme le &amp; est devenu &) :

header.php
Sélectionnez
$template->assign('title',
    "Guillaume Rossolini - Cours Web & PHP -"
        ." Club d'entraide des développeurs francophones"
);
header.tpl
Sélectionnez
<title>{$title|escape:"htmlall"}</title>

Il est également possible d'envoyer des tableaux en utilisant la même méthode :

header.php
Sélectionnez
$template->assign('meta',
    array
    (
        'generator' => 'developpez-com',
        'description' => 'Guillaume Rossolini - Cours Web & PHP',
        'keywords' => '',
        'mslocale' => 'fr-FR'
    )
);
header.tpl
Sélectionnez
    <meta name="generator" content="{$meta.generator|escape:"htmlall"}" />
    <meta name="description" content="{$meta.description|escape:"htmlall"}" />
    <meta name="keywords" content="{$meta.keywords|escape:"htmlall"}" />
    <meta name="MS.LOCALE" content="{$meta.mslocale|escape:"htmlall"}" />

Finalement, dans le code de notre script header.php, nous avons toute une série d'assignations aveugles :

Envoi de variables scalaires et de tableaux :
Sélectionnez
$template->assign('charset', $charset);
$template->assign('meta', $meta);
$template->assign('css', $css);
$template->assign('title', $title);

Blocs

Smarty propose deux manières d'utiliser des boucles dans le gabarit. Dans les exemples qui suivent, j'utiliserai strictement le même code PHP :

home.php
Sélectionnez
$template->assign('subjects', $subjects);

Dans un premier temps, voyons la section.

home.tpl
Sélectionnez
{section name=subject loop=$subjects}
    <div class="bloc_cours">
        <div class="titre_cours">{$subjects[subject].title|escape:"htmlall"}</div>
    </div>
    <br />
    <br />
{/section}

Avec ce code, Smarty comprend qu'il doit parcourir le tableau PHP $subjects afin de créer un bloc name=subject du gabarit à chaque itération. L'attribut name de la boucle est la variable utilisée comme offset du tableau $subjects.

Il ne faut pas confondre les trois variables : $subjects en PHP, $subjects en Smarty et subject pour l'indice du tableau. Ce n'est pas ma méthode préférée, à cause de cette confusion.

Une autre solution consiste à utiliser une structure foreach : c'est une manière plus simple de parcourir les tableaux qu'avec une section.

home.tpl
Sélectionnez
{foreach from=$subjects item=subject}
    <div class="bloc_cours">
        <div class="titre_cours">{$subject.title|escape:"htmlall"}</div>
    </div>
    <br />
    <br />
{/foreach}

Ici, nous avons quelque chose de plus semblable à ce dont nous avons l'habitude en PHP. L'inconvénient est que cela ne permet pas d'utiliser les options de la structure section (même si je n'en ai présenté aucune ici, sachez qu'elles existent).

Imbrication

Avec les deux exemples qui suivent, nous obtenons le même code HTML.

home.php
Sélectionnez
//
// Récupération des données
//

$sql = 'SELECT id, title
        FROM subject';
$result = mysql_query($sql) or die(mysql_error());
$i = 0;
while($subject = mysql_fetch_assoc($result))
{
    $subjects[$i] = $subject;
    $sql = 'SELECT id, title
            FROM category
            WHERE subject_id = '.$subject['id'];
    $categories = mysql_query($sql) or die(mysql_error());
    $j = 0;
    while($category = mysql_fetch_assoc($categories))
    {
        $subjects[$i]['categories'][$j] = $category;
        $sql = 'SELECT id, uri, title, description
                FROM tutorial
                WHERE category_id = '.$category['id'];
        $tutorials = mysql_query($sql) or die(mysql_error());
        $k = 0;
        while($tutorial = mysql_fetch_assoc($tutorials))
        {
            $subjects[$i]['categories'][$j]['tutorials'][$k] = $tutorial;
            ++$k;
        }

        ++$j;
    }

    ++$i;
}

//
// Envoi au gabarit
//

$template->assign('subjects', $subjects);

Voici section :

home.tpl
Sélectionnez
{section name=subject loop=$subjects}
    <div class="bloc_cours">
        <div class="titre_cours">{$subjects[subject].title|escape:"htmlall"}</div>
    {section name=category loop=$subjects[subject].categories}
        <div class="categorie_cours">{$subjects[subject].categories[category].title|escape:"htmlall"}</div>
        <div class="liste_cours">
            <ul>
        {section name=tutorial loop=$subjects[subject].categories[category].tutorials}
                <li>
                    <a href="{$subjects[subject].categories[category].tutorials[tutorial].uri|escape:"htmlall"}"
                    >{$subjects[subject].categories[category].tutorials[tutorial].title|escape:"htmlall"}</a> :
                    {$subjects[subject].categories[category].tutorials[tutorial].description|escape:"htmlall"}
            </li>
        {/section}
            </ul>
        </div>
        <hr />
    {/section}
    </div>
    <br />
    <br />
{/section}

Voici maintenant foreach :

home.tpl
Sélectionnez
{foreach from=$subjects item=subject}
    <div class="bloc_cours">
        <div class="titre_cours">{$subject.title|escape:"htmlall"}</div>
    {foreach from=$subject.categories item=category}
        <div class="categorie_cours">{$category.title|escape:"htmlall"}</div>
        <div class="liste_cours">
            <ul>
        {foreach from=$category.tutorials item=tutorial}
                <li>
                    <a href="{$tutorial.uri|escape:"htmlall"}">{$tutorial.title|escape:"htmlall"}</a> :
                    {$tutorial.description|escape:"htmlall"}
                </li>
        {/foreach}
            </ul>
        </div>
        <hr />
    {/foreach}
    </div>
    <br />
    <br />
{/foreach}

Alternance

Dans de nombreuses situations, il est souhaitable de pouvoir alterner entre deux ou trois valeurs. C'est majoritairement le cas des listes : alterner entre deux couleurs de fond permet de faciliter la lecture.

Smarty propose une solution simple : une fonction permet de le faire au sein du gabarit. Le code PHP n'est pas affecté par cet aspect de présentation, tout est laissé à la charge du graphiste.

Voici un exemple qui s'applique au code précédent :

home.tpl
Sélectionnez
{foreach from=$category.tutorials item=tutorial}
        <li>
            <a href="{$tutorial.uri|escape:"htmlall"}">{$tutorial.title|escape:"htmlall"}</a> :
            <span style='color: {cycle values="blue,green"}'>{$tutorial.description|escape:"htmlall"}</span>
        </li>
{/foreach}

En situation réelle, il faut utiliser des classes CSS et non l'attribut HTML style.

Segmentation du gabarit

Smarty, puisqu'il déporte tout ce qui est relatif à l'affichage vers le gabarit, préfère laisser PHP inclure les scripts de configuration et se charger d'inclure les fichiers de gabarit. C'est à cela que sert la fonction {include}.

Dans notre exemple, nous aurons donc ceci dans le gabarit home.tpl plutôt que dans les scripts PHP :

home.tpl
Sélectionnez
{include file="header.tpl"}

Gestion du cache

Smarty peut gérer la mise en cache du gabarit produit lors de l'exécution des scripts PHP. Utiliser la mise en cache permet d'accélérer la génération de la page puisque le document n'est pas reconstruit à partir de zéro, il suffit de réutiliser le document mis en cache.

Cependant, Smarty ne gère que l'affichage de notre page. Il ne gère ni la connexion à la base de données ni les requêtes SQL, qui consomment plus de temps processeur que la fusion des variables dans le gabarit source. Lorsque nous avons un document mis en cache, il serait donc intéressant de trouver un moyen de le charger sans exécuter les requêtes SQL (dont le retour ne sera pas utile, dans la mesure où le document est déjà prêt).

Cela se fait au moyen de la méthode is_cached() :
Sélectionnez
$template->is_cached('home.tpl')
{
    //
    // Mettre ici les requêtes de récupération des données, l'envoi des données au gabarit, etc.
    //
}

Il faut utiliser le nom du fichier de gabarit lors de cet appel : Smarty gère la mise en cache au niveau du gabarit, chacun ayant son fichier de cache indépendant.

Smarty conserve des informations amusantes dans les fichiers de cache : je ne les ai pas étudiées par manque de temps, elles ressemblent à des variables PHP sérialisées.

IV-C. Fonctionnalités avancées

Je vais présenter ici les plugins qui me semblent les plus intéressants avec Smarty. Bien entendu, vous pouvez parfaitement développer les vôtres afin d'étendre les capacités du moteur...

Plugin {mailto} : Protection d'adresses e-mail

Note : Les exemples suivants sont tirés de la documentation officielle du projet Smarty.

La syntaxe fondamentale n'est pas très différente du HTML :

*.tpl
Sélectionnez
{mailto address="me@example.com" text="Envoyez-moi un e-mail"}
HTML généré
Sélectionnez
<a href="mailto:me@example.com" >Envoyez-moi un e-mail</a>

Cela semble inintéressant car trop similaire au HTML...

Cependant, regardez maintenant :

*.tpl
Sélectionnez
{mailto address="me@example.com" encode="javascript_charcode"}
HTML généré
Sélectionnez
<script type="text/javascript" language="javascript">
    <!--
    {document.write(String.fromCharCode(60,97, ... snipped ...60,47,97,62))}
    //-->
</script>

Il va sans dire que cela peut s'avérer très pratique pour protéger une adresse e-mail contre les robots spammeurs, tant qu'ils n'interprètent pas le JavaScript des pages Web.

Plugin {debug} : Débogage

Smarty permet de déboguer votre gabarit grâce à la propriété $debugging. Il suffit de la mettre à TRUE dans votre classe personnalisée pour voir apparaître une console JavaScript lors du chargement de la page. Vous pouvez ainsi parcourir la liste des gabarits utilisés ainsi que des variables assignées.

Une autre méthode de débogage consiste à utiliser la fonction {debug} dans le gabarit lui-même, ce qui permet d'avoir les informations relatives à un gabarit particulier.

Plugin {html_table} : Tableaux HTML

Avec le plugin {html_table}, Smarty peut construire un tableau HTML à partir d'une variable PHP de type array.

Extrait de la documentation (*.php) :
Sélectionnez
$smarty->assign(
    'data', 
    array(1, 2, 3, 4, 5, 6, 7, 8, 9)
);
Extrait de la documentation (*.tpl) :
Sélectionnez
{html_table loop=$data cols=4}

Cet exemple produira un tableau comme celui-ci :

1 2 3 4
5 6 7 8
9      

Plugin {insert} : En tête du document de sortie

Une bonne pratique en PHP est de systématiquement envoyer le Content-Type avec votre document, ainsi éventuellement que d'autres headers.

Smarty vous permet de le gérer au niveau du gabarit.

Repris de la documentation (*.php) :
Sélectionnez
function insert_header($params)
{
   if(empty($params['content']))
   {
       die('Le paramètre "content" est obligatoire');
   }

   header($params['content']);
   return;
}
Repris de la documentation (*.tpl) :
Sélectionnez
{insert name=header content="Content-Type: application/xml"}

Il faut définir une fonction dont le nom commence par "insert_". À noter qu'un seul paramètre est ici envoyé à la fonction mais que l'on peut évidemment gérer davantage de situations.

Plugin {include} : Combination de gabarits

cf. ci-dessus.

Filtre de sortie

Puisque Smarty gère tout le document de sortie avec la fonction eval(), il lui est tès facile d'appliquer une fonction globale à la manière des fonctions de cache de PHP.

Si l'on souhaite appliquer un post traitement à un document, comme par exemple la méthode décrite dans mon cours de réécriture dynamique de liensTutorisl webmarketing : l'URL rewriting, il est préférable d'utiliser ce que Smarty appelle un "Output Filter" :

Exemple de filtre de sortie :
Sélectionnez
function rewrite_uris($html, &$smarty)
{
    //
    // Réécrire ici les liens contenus dans $html
    //
    
    return $html;
}

$smarty->register_outputfilter('rewrite_uris');

IV-D. Mon avis

Avantages

La classe fondamentale est simple à réutiliser. Il suffit de placer le répertoire de Smarty dans un dossier accessible par tous les utilisateurs d'un même serveur, puis de dériver la classe Smarty pour chaque site Web hébergé sur ce serveur.

Le code PHP est épuré de toute présentation. Cela le rend bien plus lisible, c'est indéniable.

Il est évident que Smarty est prévu pour utiliser une mise en cache.

Ce moteur permet de faire la distinction entre la logique applicative et la logique de présentation.

Inconvénients

PHP < 5

Pour être efficace, il faut apprendre à utiliser le langage de script de Smarty.

Le code du gabarit contient à la fois du HTML et une certaine logique applicative (celle liée à la présentation).

Conclusion

Une fois habitué à la syntaxe de Smarty, le développement va vite. Je me demande encore pourquoi avoir déplacé dans le gabarit ce qui ressemle à de la logique applicative (les boucles) mais cela a son utilité, quoique je n'en parle pas ici.

L'objectif principal est atteint : la logique applicative (récupération et traitement des informations) est séparée de la logique de présentation (affichage des informations). Le souci que je vois est que, pour y parvenir, Smarty est obligé d'utiliser du code dans les fichiers de gabarit. À mon sens, un gabarit est quelque chose de complètement passif, un outil utilisé par un autre outil, un simple moule. Avec Smarty, ce moule est actif.

Smarty dispose de plugins intéressants mais il pourrait aller bien plus loin. Par exemple, le plugin de sélection de date pourrait (devrait) produire une boîte JavaScript/AJAX sous forme d'un calendrier : ce serait une solution bien plus ergonomique que les trois <select> actuels. Je ne doute pas qu'un tel plugin ait été développé par un tiers : je ne parle pas ici des contributions qui ne sont pas intégrées dans la classe principale.

Ce qui me gêne avec Smarty est la forte similitude entre le code de void/home.php et celui de smarty/home.php (cf. plus haut). Certes, le code lié à la présentation des données n'est plus dans un script PHP... Mais y a-t-il une réelle différence ? Le graphiste de l'équipe acceptera-t-il d'apprendre la syntaxe de Smarty pour composer les interfaces ?

Il faut absolument préparer toutes les informations dans des tableaux associatifs avant de commencer à appeler la méthode assign(), sans quoi l'imbrication de boucles avec Smarty peut être un problème. Ce n'est pas un problème en soi, il faut juste y penser.

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.