Smarty, ou comment séparer le PHP du HTML

Un des reproches qui est parfois fait à PHP est de mélanger le code HTML, qui gère l’affichage de la page, au code PHP. Dans certaines applications, il n’est pas rare d’être confronté à un mélange souvent assez confus de code HTML au milieu du code PHP, en général avec la commande echo(). L’inverse est également possible : votre page HTML contient des balises <?php > pour exécuter du code.

En bref, on est loin de disposer d’un code clair et facilement maintenable.

Pour régler cet inconvénient, l’utilisation d’une classe comme Smarty (http://www.smarty.net/ permet une séparation nette du code PHP et du code HTML.

Son principe est le suivant :

Figure 1: Diagramme de séquence : utiliser Smarty pour afficher une page

L’utilisateur appelle un module, qui lui permettra d’obtenir une liste des personnes. Le module fait appel à une instance de classe, ici personne, pour récupérer la liste correspondant aux paramètres fournis.

Une fois que le module a récupéré la liste, il l’assigne à l’instance de Smarty, en lui donnant un nom de variable. Cette opération peut être répétée autant de fois que nécessaire pour transmettre toutes les données.

Quand le module a fini de traiter les informations, il demande à Smarty d’afficher la page web, en lui indiquant le modèle, ou template, à utiliser.

La classe Smarty va alors traiter les informations et créer une page temporaire, qui contiendra à la fois le code html et le code php nécessaire pour générer l’affichage. Cette page sera exécutée pour être envoyée au navigateur.

Pour utiliser Smarty, nous devons donc d’abord instancier sa classe et lui fournir les données à afficher, puis dessiner notre modèle de page : c’est une page html qui contient du code spécifique, interprété par Smarty pour gérer l’affichage des informations.

Instancier la classe Smarty dans PHP

Voici tout d’abord un petit aperçu du fonctionnement de Smarty. Comme nous l’avons vu, nous disposons de deux fonctions principales : assign() et display().

La première, assign("nom_variable", $contenu), permet de transmettre à Smarty les données qui devront être manipulées.

La seconde, display("nom_du_modele"), va déclencher l’affichage. Cet affichage va être réalisé en plusieurs étapes :

  • d’abord, Smarty va créer un fichier PHP qui contiendra d’une part les balises HTML, et d’autre part des commandes PHP pour afficher les variables décrites dans le modèle. Cette étape est appelée compilation ;
  • ensuite, Smarty appellera le fichier compilé (le fichier PHP généré automatiquement), qui traitera les balises HTML et inclura les données fournies par PHP.

Voici un exemple d’un fichier compilé. Il s’agit du début du script utilisé pour saisir la fiche d’un utilisateur d’une application :

<?php

/*

Smarty version Smarty-3.1.8, created on 2012-05-16 11:01:30 compiled from "smarty/templates/ident/loginsaisie.tpl"

*/

?>

<?php

if(!defined('SMARTY_DIR'))

exit('no direct access allowed');

$_valid = $_smarty_tpl->decodeProperties(array ( 'file_dependency' => array ( '16e9473aec888f5b83ab8120546c3bf1f5624cb2' => array ( 0 => 'smarty/templates/ident/loginsaisie.tpl', 1 => 1337157699, 2 => 'file', ), ),

'nocache_hash' => '4059024494fb36ceab0bea6-82981549',

'function' => array ( ),

'variables' => array ( 'list' => 0, 'LANG' => 0, ),

'has_nocache_code' => false,

'version' => 'Smarty-3.1.8',

'unifunc' => 'content_4fb36ceabebe14_82165932', ),false);

?>

<?php

if ($_valid && !is_callable('content_4fb36ceabebe14_82165932')) {

function content_4fb36ceabebe14_82165932($_smarty_tpl) {

?>

<form method="post" action="index.php">

<input type="hidden" name="action" value="M">

<input type="hidden" name="id" value="<?php echo $_smarty_tpl->tpl_vars['list']->value['id'];?> ">

<input type="hidden" name="module" value="loginmodif">

<input type="hidden" name="password" value="<?php echo $_smarty_tpl->tpl_vars['list']->value['password'];?> ">

<table class="tablesaisie">

<tr>

<td>

<?php echo $_smarty_tpl->tpl_vars['LANG']->value['login'][0];?>

:</td>

<td>

<input name="login" value="<?php echo $_smarty_tpl->tpl_vars['list']->value['login'];?> ">

</td>

</tr>

(...)

La première partie du fichier décrit les conditions de compilation. Notez la commande

if(!defined('SMARTY_DIR'))

exit('no direct access allowed');

qui interdit l’accès direct à la page compilée.

Les données sont affichées par l’intermédiaire de la commande php echo.

Cette phase de compilation implique que Smarty, et donc le processus ou l’utilisateur système qui gère le serveur web, puisse écrire dans le dossier contenant les fichiers compilés. Pour cela, vous serez probablement obligés, avec Linux, de taper une commande de ce type :

setfacl -R -m u:www-data:rwx templates_c

Il s’agit d’une commande de modification des acl (Access Control List), un mécanisme qui permet de définir des droits dans les serveurs Linux.

Voyons maintenant comment utiliser Smarty.

Dans un premier temps, nous créons une instance : ,

$smarty = new Smarty ();

puis nous allons lui indiquer où sont placés les dossiers contenant les modèles et les fichiers compilés :

$smarty->template_dir = display/templates;

$smarty->compile_dir = display/templates_c;

Ici, les deux dossiers sont placés dans le répertoire display. Ce mécanisme est particulièrement intéressant : les appels aux modèles sont réalisés avec un adressage relatif par rapport au dossier template_dir. Cela simplifie le développement et vous permet, le cas échéant, de déplacer le dossier templates avec un coût minimum : vous n’aurez besoin que de redéfinir le chemin d’accès.

Il est également possible de définir un cache :

$smarty->cache_dir = display/smarty_cache;

$smarty->caching = false;

Le cache permet d’éviter de recompiler les modèles à chaque appel. En général, il n’est pas activé pendant le développement : dans le cas contraire, les modifications ne seront pas prises en compte tant qu’il n’aura pas été vidé.

Pour la version de production, faites des tests auparavant pour vous assurer que cela n’entraîne pas d’effets indésirables. Ainsi, si vous n’utilisez qu’un seul modèle principal qui contient lui-même des sous-modèles, comme nous le verrons un peu plus loin, le cache ne conservera que la première page appelée ; le mécanisme n’est donc pas applicable dans ce cas.

Associer les modèles avec l’éditeur HTML

Par convention, les modèles prennent l’extension tpl. Les modèles sont d’abord des fichiers contenant des commandes html : pour faciliter leur écriture, vous pouvez associer l’extension .tpl avec l’éditeur html de votre environnement de développement.

Si vous travaillez avec Eclipse (http://www.eclipse.org/pdt/#download, un des environnements de développement multi plate-forme les plus répandus, voici la procédure à suivre.

Dans le menu, choisissez Windows > Preferences. Dans l’arbre, positionnez-vous dans General > Appearence > Content Types. Dans la zone de droite (Contents types), positionnez-vous sur Text > HTML. Enfin, cliquez sur le bouton Add..., et rajoutez l’extension *.tpl. Fermez la fenêtre grâce au bouton OK : tous vos modèles bénéficieront de l’éditeur HTML d’Eclipse, avec sa coloration syntaxique et la vérification de la syntaxe.

Figure 2: Configurer Eclipse pour associer l’éditeur HTML avec les fichiers *.tpl

Créer un modèle simple - la syntaxe de base

Un modèle Smarty est d’abord un fichier HTML, auquel sont ajoutés des commandes qui seront interprétées par la classe. Voici un premier exemple :

,

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>{$titre}</title>

<link rel="stylesheet" href="{$fichier_css}" type="text/css">

<link rel="icon" type="image/png" href="favicon.png" />

</head>

<body>

Bonjour, {$nom} !

</body>

</html>

les variables, comme toutes les commandes Smarty, sont encapsulées dans des accolades, qui doivent être «collées » au code (sans espace entre les accolades et la commande de Smarty). Elles doivent être précédées du signe dollar $.

Nous avons utilisé trois variables différentes : $titre permet de définir le titre de la fenêtre dans le navigateur, $fichier_css représente le nom de la feuille de style que nous allons utiliser, et $nom le texte que nous voulons afficher.

Voici un extrait du fichier PHP qui permet de générer une telle page : ,

$smarty = new Smarty() ;

$smarty->assign("titre", "Mon application") ;

$smarty->assign("fichier_css", "display/css/modele.css");

$smarty->display("monModele.tpl");

Smarty dispose d’un langage particulièrement riche. Nous allons nous intéresser aux commandes les plus utiles.

Réaliser des tests

Les tests sont assez classiques, et ne posent guère de difficultés. Voici un test analysant le contenu d’une variable (transmise à Smarty avec la fonction assign()) : ,

{if $admin==1} {$nom} est administrateur {elseif $gestion==1} {$nom} est un gestionnaire {else} {$nom} est un utilisateur {/if}

Voici quelques règles simples à respecter :

  • toute commande {if} doit se terminer par {/if} (else est facultatif) ;
  • les tests sont ceux que vous pourriez réaliser en PHP :
          • {if strlen($nom) > 0} (...)
      • vous permet de vérifier la longueur de la variable $nom ;
  • vous devez également séparer l’affichage des variables des tests eux-mêmes : chaque type de traitement (test, affichage de données...) doit être inclus dans son propre jeu d’accolades ;
  • Les variables sont celles assignées par la fonction assign(), et non les variables de PHP. Elles doivent être impérativement précédées du signe dollar $, y compris dans les instructions de test.

Traiter les occurrences d’un tableau de données

Smarty possède une fonction très puissante pour traiter les tableaux de données, bien que pas très intuitive. Prenons d’abord le code PHP qui nous permet de récupérer une liste d’enregistrements dans une table, par exemple la liste des personnes : ,

$personne = new Personne(); $data = $personne->getListe($param); $smarty->assign ("data", $data);

Dans ce script, nous avons instancié la classe Personne. Celle-ci contient une fonction qui permet de récupérer une liste de personnes, en fonction des paramètres fournis : la fonction crée une requête SQL et retourne un tableau.

Ce tableau est de la forme :

$data = array (

array( "nom" => "Dupont", "prenom" => "Jean"),

array ( "nom" => "Durand", "prenom" => "Jacques"),

array ( "nom" => "Martin", "prenom" => "Evelyne")

);

nom et prenom sont les noms des colonnes fournis par la requête SQL. Chaque enregistrement est stocké dans le premier niveau du tableau $data.

Voyons maintenant comment nous allons afficher l’ensemble de la liste, par exemple dans une table HTML. Commençons par définir l’entête du tableau :

,

<table>

<tr>

<th>Nom</th>

<th>Prénom*)</th>

<tr>

Maintenant, nous allons créer une boucle au moyen de l’instruction section de Smarty :

{section name=iter loop=$data}

<tr>

<td>{$data[iter].nom}</td>

<td>{$data[iter].prenom}</td>

</tr>

{/section}

</table>

L’instruction section a besoin de deux arguments pour fonctionner : name définit le nom de la variable qui sera utilisée pour traiter l’occurrence, et loop contient le tableau manipulé.

Chaque valeur à afficher est définie ensuite par :

  • le nom du tableau manipulé, ici $data,
  • le nom de l’occurrence en cours (iter),
  • et enfin, l’attribut à afficher (nom et prenom).

Comme pour la plupart des instructions Smarty, pensez à fermer votre boucle par l’instruction {/section}.

Cette méthode peut également être utilisée pour renseigner les options d’une balise <select>, qui permet de sélectionner un enregistrement dans une liste déroulante. Voici un exemple pour sélectionner une civilité :

<select name="civilite_id" >

<option value="" {if $data.civilite_id == ""}selected{/if}> Sélectionnez la civilité... </option>

{section name=iter loop=$civilites}

<option value="{$civilites[iter].civilite_id}" {if $civilites[iter].civilite_id == $data.civilite_id}selected{/if}>

{$civilites[iter].civilite_libelle}

</option>

{/section}

</select>

Ce script permet d’afficher la liste déroulante avec le libellé Sélectionnez la civilité si aucune information n’a été remplie préalablement. Dans le cas contraire, il positionne la liste déroulante au niveau de la civilité sélectionnée.

$civilites contient la liste des civilités disponibles, et $data les données de la fiche en cours de modification.

Nous avons vu ici les attributs de base de la commande section. Il est toutefois possible d’obtenir d’autres informations facilement, comme le numéro de l’occurrence courante, ou le nombre total d’enregistrements. Pour aller plus loin, consultez l’aide en ligne (et en français) : http://www.smarty.net/docsv2/fr/language.function.section.tpl.

La documentation de la version 3 n’est pas traduite, mais elle ne présente pas d’évolution marquante par rapport à la version 2, et la documentation est quasiment identique.

Assigner des variables

Si vous avez besoin de réaliser quelques calculs dans une boucle, vous devrez probablement passer par la fonction {assign}. Elle requiert deux attributs : var, qui contient le nom de la variable, et value, qui contient la valeur à assigner.

Voici un exemple de calcul à l’intérieur d’une section :

{assign var=total value=0}

{section name=iter loop=$data}

(...)

{assign var=total value=$total + $data[iter].montant}

{/section}

Montant total : {$total}

la variable total est incrémentée, lors du traitement de chaque occurrence, de la valeur indiquée dans l’attribut $data[iter].montant, et le résultat est affiché après le traitement du tableau.

Faire cohabiter Smarty et Javascript

Comme nous l’avons vu, les variables et commandes de Smarty sont encapsulées dans des accolades. Cela n’est pas sans poser un problème si nous voulons utiliser du Javascript : les fonctions Javascript sont, elles aussi, encapsulées dans des accolades...

Avant la version 3 de Smarty, il n’y avait pas de solution très pratique. Soit votre code Javascript était systématiquement stocké dans des fichiers distincts de votre page HTML (les commandes Javascript n’étaient alors pas traitées lors de la phase de compilation de Smarty), soit vous deviez redéfinir les délimiteurs pour Smarty.

Avec la version 3, les développeurs de Smarty ont trouvé une solution élégante : si l’accolade ouvrante ({) est collée à un autre caractère, alors le compilateur Smarty considère qu’il s’agit d’une instruction Smarty. Si elle est suivie par un espace vide (ou un retour à la ligne), alors le compilateur Smarty l’ignore.

En d’autre terme, tapez systématiquement un espace après les accolades que vous écrivez dans votre code Javascript, et collez vos commandes Smarty à l’accolade ouvrante pour qu’elles soient traitées.

Voici un exemple mixant les deux approches. D’abord, un bout de code PHP : ,

$smarty->assign("nom", "Eric Quinton"};

puis le code JQuery dans notre page HTML :

<script>

$(document).ready(function() {

alert("Bonjour, {$nom} !");

});

</script>

À l’ouverture de votre page, JQuery va déclencher l’affichage de cette fenêtre :

Figure 3: Un message combinant du Javascript et une variable Smarty

Appeler d’autre modèles depuis un modèle principal

Une des grandes forces de Smarty est de permettre l’insertion d’un modèle dans un autre modèle. Cette opération est réalisée avec l’instruction suivante :

{include file="monSousModele.tpl"};

Cette fonctionnalité va, dans un premier temps, permettre de créer des modèles pour chaque partie de votre page. Ainsi, si votre page HTML contient une boite de sélection et une liste, vous pourrez créer un modèle pour chaque objet manipulé, et un général. Celui-ci contiendra alors les commandes :

{include file="monModule/selection.tpl"}

<br>

{include file="monModule/liste.tpl"}

Mais il est possible de faire mieux... Voici un exemple de mise en page d’une application :

Figure 4: La structure générale de la page web

Le bandeau supérieur contient en général une icône, le titre de l’application et, pourquoi pas, un menu. Le pied de page, quant à lui, rappelle la licence, qui contacter en cas de souci, etc. Ces deux informations sont strictement identiques d’une page à l’autre (le menu peut toutefois changer et être recalculé entre deux pages). Seul le contenu de la page, c’est à dire la partie réellement utile, va être modifiée.

Smarty vous permet de ne créer qu’une seule page, qui contiendra trois sous-modèles : une entête, un pied de page, et un corps. Ce dernier sera variable en fonction du module appelé.

L’avantage d’une telle approche est que votre page est toujours identique dans l’ensemble de votre application : si vous voulez modifier une icône, ajouter un bouton ou un texte dans l’entête ou le pied de page, ou déclarer une bibliothèque Javascript ou JQuery commune à l’ensemble de votre application, rien de plus facile !

Voici un exemple d’organisation pour gérer cet aspect. Le code PHP permettant d’appeler la page est le suivant : ,,

/*

* Definition des parametres

*/

$SMARTY_entete = "entete.tpl";

$SMARTY_enpied = "enpied.tpl";

$SMARTY_corps = "main.tpl";

$APPLI_fds = "display/css/blue.css";

(...)

/*

* Assignations par defaut

*/

$smarty->assign ( "fds", $APPLI_fds );

$smarty->assign ( "entete", $SMARTY_entete );

$smarty->assign ( "enpied", $SMARTY_enpied );

$smarty->assign ( "corps", $SMARTY_corps );

(...)

/*

* Declenchement de l'affichage

*/

$smarty->display ("main.htm");

Le code PHP est séparé en trois parties. La première permet de définir les modèles par défaut, ainsi que la feuille de style, la seconde partie assigne les variables à Smarty. Ces deux parties peuvent, d’ailleurs, être séparées et appelées à différents endroits dans l’application. Enfin, le programme se termine par le déclenchement de l’affichage de la page, par la fonction display().

Voyons maintenant ce que contient notre page main.htm :

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>{$titre}</title>

<link rel="stylesheet" href="{$fds}" type="text/css">

<link rel="icon" type="image/png" href="favicon.png" />

</head>

<body>

{include file="jquery.tpl"}

{include file=$entete}

{include file=$corps}

{include file=$enpied}

</body>

</html>

Notre modèle est une page HTML complète, avec la présence des balises <html>, <head> et <body>. Il inclut quatre sous-modèles : jquery.tpl déclenche le chargement des bibliothèques JQuery, et les trois autres correspondent à chaque partie de la page.

Par défaut, la variable Smarty $corps correspond à un modèle d’accueil, appelé ici main.tpl. Mais rien ne vous empêche, au gré de vos modules, de modifier le contenu de cette variable pour afficher d’autres informations : il vous suffit, dans un module, de rajouter la commande

$smarty->assign("corps", "module/liste.tpl") ;

pour demander à Smarty d’afficher la liste dessinée dans le modèle liste.tpl.

Les avantages d’une telle approche sont nombreux. D’une part, vous ne décrivez qu’une seule page HTML : cela vous garantit une mise en page identique d’un bout à l’autre de l’application. D’autre part, si vous faites évoluer votre pied de page ou votre entête, toute l’application en bénéficie immédiatement. Enfin, vos modèles sont très simples à concevoir : vous n’avez besoin que de vous concentrer sur le contenu spécifique à afficher, sans qu’il soit nécessaire de penser à tout l’affichage périphérique (menus, pied de page...).

C’est également un mécanisme qui prend tout son intérêt quand l’application est bâtie selon le modèle MVC (modèle, vue, contrôleur). En effet, l’envoi de la page au navigateur peut être unique, et les modules qui seront développés auront simplement besoin d’indiquer quel modèle devra être inséré dans l’afficheur.

Il y a quand même un inconvénient à utiliser cette approche : vous ne pourrez pas employer le mécanisme de cache de Smarty. En effet, la page qui est appelée ne change jamais, ce n’est que le contenu de la variable $corps qui évolue. Mais ce n’est pas vraiment un gros problème, les différences de performance que cela pourrait occasionner sont très minimes avec la puissance des machines actuelles.