Si vous codez sur PHP depuis quelques temps, vous avez certainement déjà utilisé un moteur de templates. Mais était-il adapté à votre projet ? Je vous propose ici d’apprendre à créer le votre, toujours simplement.

Avant toute chose, je précise que cet article n’est qu’une piste, une ébauche pour créer un tel système. A la fin de celui-ci, vous aurez une version utilisable du mécanisme, mais en aucun cas il n’est une bonne pratique, pour les raisons que je détaillerai plus bas.
Et puis, je ne tiens pas à entrer dans une polémique trollesque du « Mais PHP est déjà un moteur de template ». Non, je l’ai assez entendu.

Un moteur de templates ?

Si vous ne connaissez pas, sachez que c’est un système qui permet de séparer le code PHP d’une page de son design. C’est à dire que dans votre vue, appelée template, il n’y aura que du HTML et des variables PHP déguisées.

PS : Rangez vos bâtons, ce que j’appelle design ici regroupe tout le code HTML, donc pas uniquement le design mais aussi l’UI, l’ergonomie, la structure, tout en fait.

Voilà à quoi un template peut ressembler :

Même si vous n’en utilisez pas, vous en connaissez sûrement :

Twig

twig

Twig est à l’heure actuelle le plus répandu des moteurs de templates PHP. Créé par SensioLabs, la boite qui supporte Symfony, celui-ci est devenu LA référence et le moteur de templates fournit par défaut avec le framework.

Smarty

smarty

Smarty est le plus vieux des moteurs de templates. Je n’ai pas la date de sortie de sa première version, mais sait que la 2.6 est sortie en.. 2003 ! Et oui, ça remonte déjà.. Comme Twig, il offre un large panel de fonctions, des filtres, gestion du cache etc

Quels sont les avantages ?

Aux premiers abords, j’aime dire qu’un moteur de templates rend la vue plus agréable à lire et à comprendre, mais aussi à maintenir. Vous avez le script d’un coté, le design de l’autre.
Qui plus est, pas de <?php echo $var; ?> partout, uniquement une syntaxe définie. Si vous bossez avec un web-designer qui est limite avec le PHP, il sera certainement le plus ravi de tous de l’utilisation d’un tel système.
Enfin, point non négligeable, les moteurs de templates intègrent généralement une gestion du cache, ce qui permet d’afficher une page sans avoir à la traiter à chaque fois.

Oui mais

Personnellement, j’adore les moteurs de templates, je ne travaille plus sans. Aussi je n’ai sûrement pas une vision très objective. Quoi qu’il en soit, il faut avouer que ces librairies font perdre légèrement en terme de performance (rien de sensible pour l’humain), et qu’elles augmentent plus ou moins le nombre de fichiers dans votre projet.
Mais c’est vraiment peu comparé au gain final 😉

Pourquoi créer le sien ?

Étant autodidacte dans l’âme depuis tout petit (je n’ai jamais suivi un seul cours d’informatique), j’aime créer mes propres scripts (Do It Yourself powa 😉 ), surtout parce que j’apprends de nouvelles pratiques et techniques à chaque fois.
Surtout, je considère que les moteurs de templates que j’ai évoqué plus haut sont trop lourds, trop complets et s’occupent de trop de choses : Si je dois convertir une date, c’est au controlleur de faire ça, pas à la vue, tout comme échapper du code, nettoyer des données, …

Bien sûr, il y a des fonctionnalités intéressantes, mais pour 95% des applications, je ne me sert que des variables et des boucles.

Entrons à présent dans le vif du sujet.

Cahier des charges

Pour cette démonstration, je souhaite mettre en place les choses essentielles à tout bon moteur de templates, c’est à dire :

  • Créer un template à partir d’un fichier
  • Lui assigner des variables
  • L’afficher

Coté template, je souhaite :

  • Pouvoir afficher des variables de tous types (string, array, objets)
  • Avoir un niveau de profondeur infini sur les variables, c’est à dire utiliser des tableaux multidimensionnels, des méthodes et attributs des objets
  • Utiliser des conditions simples
  • Faire des boucles
  • Commenter mon template

Point important, j’utiliserai pour mes templates la syntaxe de Twig. Pourquoi ? Car c’est une syntaxe connue, aussi je ne me perdrai pas lorsque j’écrirai mes templates, que je pourrai également utiliser avec Twig du coup, et surtout, les IDE proposent maintenant l’autocomplétion pour les templates Twig 🙂

Il me semble que ce sera déjà pas mal, non ? 😉

Comment ça fonctionne ?

Nous allons tout d’abord créer une classe Tpl.
Celle-ci devra accepter un nom de fichier, le template.
On pourra alors assigner des variables à notre objet.
Lorsque l’utilisateur voudra afficher le template, l’objet récupérera le contenu du fichier, le parsera afin de transformer notre syntaxe en code PHP compréhensible. Enfin, elle évaluera le code PHP.
On utilisera les fonctions de temporisation de sortie pour ne pas afficher en temps réel le code interprété.

Ça peut paraitre compliqué mais vous verrez qu’il n’y a rien de difficile là dedans si vous êtes un peu à l’aise avec les Regex.

J’ai la classe

Créons notre classe Tpl :

Celle-ci est vide pour le moment, mais ça ne va pas durer longtemps 😉

Les attributs

Voici les attributs nécessaires au bon fonctionnement de notre classe :

Les commentaires devraient être suffisants.
En revanche, j’ai volontairement mis les attributs en protected pour ne pas qu’ils soient publics, mais également parce que je souhaite que ma classe soit étendue. Vous comprendrez l’utilité plus tard.

Le constructeur

Le constructeur de la classe ne devra accepter qu’un argument : le chemin complet du fichier template.
On testera l’existence du fichier, et s’il n’existe pas, on lancera une nouvelle exception :

En revanche, si le fichier existe, on stocke le chemin du fichier dans l’attribut filepath.

Assigner des variables

Pour être utile, il faudra pouvoir assigner  des variables aux templates. Pour ce, on stockera toutes ces données dans l’attribut data :

Cette fonction très simple servira à ajouter des variables utilisables dans notre template. Ces variables peuvent être de tous types, exceptées les closures.

La fonction de sortie

Nous allons à présent écrire la fonction de sortie, celle qui sera appelée par le script lorsqu’on voudra récupérer le code parsé du template :

Le code est, comme d’habitude, assez simple et les commentaires indiquent assez bien ce que fait la méthode.

A l’appel de la méthode, on enclenche la temporisation de sortie afin de ne pas afficher le code qui sera lu.
On récupère ensuite le contenu du fichier template défini dans le constructeur, que l’on parsera.
Enfin, on fait un eval afin d’exécuter le contenu, avant de libérer le tampon que l’on retourne.

En revanche, vous pouvez constater que la fonction fait appel à d’autres méthodes que nous n’avons pas encore implémenté. Faisons cela de suite :

Récupérer le contenu

Nous allons écrire la fonction get_content(), qui ira récupérer le contenu du fichier template et le retournera :

Cette méthode est définie privée car nul besoin d’y accéder par le script. En revanche, on peut imaginer une autre classe qui étendrait notre classe Tpl et qui la surchargerait.

Parser : 1ère partie

Écrivons à présent la première partie de notre parser.
En premier lieu, nous ajouterons la possibilité d’utiliser une variable dans notre template, préalablement passée au template via la fonction set().
Dans notre template, une variable sera définie par cette syntaxe :

Chaque variable qui devra être affichée sera entourée de 2 accolades {{ variable }}. Nous prendrons en compte également le fait de pouvoir espacer ou pas les accolades du nom de la variable.

Cette fonction, qui sera constituée uniquement d’opérations de Regex, est chargée de parser le contenu. Ici, le preg_replace() va remplacer toutes les occurrences de {{ variable.child.name }} en <?php $this->_show_var('variable.child.name'); ?>

Lorsque le code compilé sera interprété, la méthode _show_var() sera donc appelée. Celle-ci ne devra assumer qu’une seule fonctionnalité : Afficher le contenu de la variable.

Afficher une variable

Vous verrez que cette méthode sera plus alambiquée qu’on ne pourrait le croire : A première vue, un echo $variable; serait suffisant. Mais n’oublions pas que nous souhaitons pouvoir afficher des tableaux à plusieurs dimensions, des attributs de méthodes etc :

Un simple echo en effet, suivi d’une nouvelle méthode, que nous allons implémenter tout de suite. Vous en comprendrez alors son usage.

Récupérer une variable

Je vous donne le code de la méthode, j’explique ensuite :

Cette méthode accepte 2 arguments : Le nom de la variable, et son parent. Etant donné que toutes les variables ont été assignées au template, chaque variable a pour parent l’attribut data de notre objet Tpl.
Mais dans chaque variable, chaque point déterminera que la partie située à sa droite est un enfant de la variable : {{variable.child1.child2.child3. ... }}. Ici, child2 est le parent de child3, child1 est le parent de child2, variable est le parent de child1. Enfin, variable a pour parent le fameux attribut data.

En premier lieu, la variable sera ‘explosée’, en utilisant le ‘.’ comme séparateur.
Si le nombre de pièces est égal à 1, cela signifie qu’il n’y a pas de ‘.’ et donc pas d’enfant. Donc le nom de la variable est celui à récupérer.

En revanche, si un ou plusieurs enfants sont trouvés :
On extrait la première pièce du tableau $parts. Celle-ci est le nom du parent. On va donc récupérer le parent grâce à la fonction getSubVar(), que nous verrons par la suite.
Ensuite, nous réunissions toutes les pièces de la variable afin de reconstituer la variable décomposée, puis on relance cette même méthode, en indiquant le nouveau nom de variable et le nouveau parent, récupéré 3 lignes plus haut.

Récupérer l’enfant

Cette méthode est chargée uniquement de récupérer un enfant du parent, c’est à dire un attribut d’un objet, un sous-tableau d’un tableau, etc :

Comme tout à l’heure, la méthode accepte 2 paramètres : le nom de la variable à récupérer, et son parent.
Le traitement n’est alors qu’une succession de tests pour déterminer le type du parent, de l’existence et du type de la variable.
Si celle-ci est trouvée, elle est retournée. Dans le cas contraire, on renvoie une chaine vide.

Premier test

A présent, notre classe est prête pour un essai.

Passons au code. J’ai créé une petite classe Article pour tester l’appel à une méthode :

Et voilà le script :

Rien de compliqué encore une fois, j’initie des variables, un tableau et 2 objets que je passe au template via la fonction set().

Enfin, nous allons créer un template, le fichier HTML qui recevra nos variables. J’ai appelé celui-ci index.tpl, mais vous pouvez lui donner l’extension que vous souhaitez :

En lançant le script, vous devriez voir le résultat à l’écran :
tpl1

C’est pas beau ?

Cet article s’arrête ici, je publierai la suite dans quelques jours dans un autre billet, histoire qu’il ne devienne pas encore plus long qu’il ne l’est déjà, afin de traiter les points suivants comme les boucles et les tests de condition.

Retrouvez le code complet de la classe sur ce Gist.