IV. Smarty▲
IV-A. Présentation▲
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).
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.
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 :
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▲
$template
->
assign('
title
'
,
"
Guillaume Rossolini - Cours Web & PHP -
"
.
"
Club d'entraide des développeurs francophones
"
);
<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 & est devenu &) :
$template
->
assign('
title
'
,
"
Guillaume Rossolini - Cours Web & PHP -
"
.
"
Club d'entraide des développeurs francophones
"
);
<title>{$title|escape:"htmlall"}</title>
Il est également possible d'envoyer des tableaux en utilisant la même méthode :
$template
->
assign('
meta
'
,
array
(
'
generator
'
=>
'
developpez-com
'
,
'
description
'
=>
'
Guillaume Rossolini - Cours Web & PHP
'
,
'
keywords
'
=>
''
,
'
mslocale
'
=>
'
fr-FR
'
)
);
<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 :
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 :
$template
->
assign('
subjects
'
,
$subjects
);
Dans un premier temps, voyons la section.
{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.
{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.
//
// 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 :
{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 :
{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 :
{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 :
{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).
$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 :
{mailto address="me@example.com" text="Envoyez-moi un e-mail"}
Cela semble inintéressant, car trop similaire au HTML…
Cependant, regardez maintenant :
{mailto address="me@example.com" encode="javascript_charcode"}
<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.
$smarty
->
assign(
'
data
'
,
array(1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
)
);
{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.
function insert_header($params
)
{
if(empty($params
[
'
content
'
]
))
{
die('
Le paramètre "content" est obligatoire
'
);
}
header($params
[
'
content
'
]
);
return;
}
{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 trè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, 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 » :
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 ressemble à 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.