I. Introduction▲
Twig est un moteur de templating, utilisé par Symfony2 en parallèle avec des templates en PHP pur. L'avantage principal d'un moteur de templating est de séparer sans ambiguïté la présentation des données et leur traitement.
Cet article se destine à des utilisateurs ayant déjà une certaine expérience de Twig, souhaitant l'étendre par la création d'extensions.
II. L'interface▲
Une extension Twig est une classe qui implémente cette interface (code repris directement du framework) :
interface Twig_ExtensionInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*
*
@param
Twig_Environment
$environment
The current Twig_Environment instance
*/
public
function
initRuntime(Twig_Environment $environment
);
/**
* Returns the token parser instances to add to the existing list.
*
*
@return
array
An
array
of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
*/
public
function
getTokenParsers();
/**
* Returns the node visitor instances to add to the existing list.
*
*
@return
array
An
array
of Twig_NodeVisitorInterface instances
*/
public
function
getNodeVisitors();
/**
* Returns a list of filters to add to the existing list.
*
*
@return
array
An
array
of filters
*/
public
function
getFilters();
/**
* Returns a list of tests to add to the existing list.
*
*
@return
array
An
array
of tests
*/
public
function
getTests();
/**
* Returns a list of operators to add to the existing list.
*
*
@return
array
An
array
of operators
*/
public
function
getOperators();
/**
* Returns a list of global functions to add to the existing list.
*
*
@return
array
An
array
of global functions
*/
public
function
getGlobals();
/**
* Returns the name of the extension.
*
*
@return
string
The extension name
*/
public
function
getName();
}
Tout n'est pas à implémenter, bon nombre de ces fonctions le sont déjà dans Twig_Extension, il suffit d'hériter de cette dernière pour simplifier le code.
III. Première méthode et enregistrement▲
La première méthode que nous implémenterons rendra simplement le nom de l'extension, celui-ci doit être unique. Votre classe devrait ressembler à ceci :
<?php
namespace
App\AppBundle\Twig\Extension;
use
Symfony\Component\HttpKernel\KernelInterface;
use
Symfony\Bundle\TwigBundle\Loader\FilesystemLoader;
class
AppExtension extends
\Twig_Extension
{
public
function
getName()
{
return
'twigext'
;
}
}
Peu importe où vous mettez ce fichier, il suffit de le renseigner à Twig pour que l'extension soit disponible. Par exemple, vous pouvez lui consacrer un dossier Twig/Extension dans un de vos bundles ou créer un dossier Twigext (du nom de l'extension) dans votre bundle d'extensions Twig, tout dépend de la taille de votre application, du besoin en réutilisation des extensions et d'autres facteurs propres à votre application.
III-A. Intégration à Twig▲
Pour enregistrer votre extension, deux cas se présentent. Le premier est celui où vous utilisez Twig en standalone :
III-B. Intégration à Symfony2▲
Pour une intégration à Symfony2, c'est plus compliqué, il faut utiliser le principe DependencyInjection, très puissant mais pas forcément simple à manipuler au début.
Pour cela, le mieux est de placer tout ce qui concerne l'injection de dépendances dans un répertoire spécifique, DependencyInjection à la racine de votre bundle par exemple. Créons-y un fichier du nom du bundle qui contient l'extension, soit AppExtension dans notre cas. Notez que l'on définit alors le bundle courant comme une extension de Symfony2. Par exemple, si votre bundle s'appelle AppBundle, ce fichier devra s'appeler AppExtension. AppBundle correspond au fichier de bundle créé à la racine dudit bundle (le fichier créé par la ligne de commande lors de l'initialisation d'un bundle).
Tout se déroule dans la fonction load(). Nous allons faire au plus simple : simplement donner un fichier de configuration dans lequel tout sera mis. C'est aussi la méthode la plus propre, l'autre consistant à définir chaque extension directement dans ce fichier PHP. Nous verrons aussi comment faire juste après.
<?php
namespace
App\AppBundle\DependencyInjection;
use
Symfony\Component\HttpKernel\DependencyInjection\Extension;
use
Symfony\Component\DependencyInjection\ContainerBuilder;
use
Symfony\Component\DependencyInjection\Definition;
class
AppExtension extends
Extension
{
public
function
load(array
$configs
,
ContainerBuilder $container
)
{
$loader
=
new
YamlFileLoader($container
,
new
FileLocator(__DIR__.
'/../Resources/config'
));
$loader
->
load('services.yml'
);
}
}
Maintenant, il va falloir mettre ce qu'il faut dans ce fichier de configuration Resources/config/services.yml à la racine de votre bundle :
services
:
twig.extension.twigext
:
class
:
App\AppBundle\Twig\Extension\AppExtension
tags
:
-
{
name
:
twig.extension }
La méthode sans fichier de configuration se résume à ce genre de fichier d'injection de dépendance :
<?php
namespace
Bundle\AppBundle\DependencyInjection;
use
Symfony\Component\HttpKernel\DependencyInjection\Extension;
use
Symfony\Component\DependencyInjection\ContainerBuilder;
use
Symfony\Component\DependencyInjection\Definition;
class
AppExtension extends
Extension
{
public
function
load(array
$config
,
ContainerBuilder $container
)
{
$definition
=
new
Definition('App\AppBundle\Extension\AppExtension'
);
$definition
->
addTag('twig.extension'
);
$container
->
setDefinition('app_twig_extension'
,
$definition
);
// le nom de l'extension renseigné dans "getName" de l'extension Twig
}
}
IV. Implémentation▲
Dernière étape : l'implémentation de votre extension. Ces snippets sont à mettre dans votre fichier d'extension Twig, soit dans le fichier du dossier Twig/Extension si vous avez suivi mes conseils.
IV-A. Fonctions▲
Cette extension Twig peut comporter des fonctions, qui sont enregistrées comme suit :
public function getFunctions()
{
return array(
'
fc
'
=>
new \Twig_Function_Method($this
,
'
fc
'
,
array('
is_safe
'
=>
array('
html
'
))),
'
fc2
'
=>
new \Twig_Function_Method('
fc2
'
,
array('
is_safe
'
=>
array('
html
'
))),
);
}
public function fc($txt
)
{
// votre code
}
Dans le dernier cas, il s'agira de la fonction fc2, elle doit être accessible dans ce fichier (définie à la suite ou include).
La fonction appelée doit renvoyer directement ce qui doit être mis dans le contenu envoyé au navigateur.
IV-B. Filtres▲
Un filtre est une fonction qui prend en paramètre un certain texte et le renvoie transformé. Par exemple, une date, qui est rendue dans un autre format. Un filtre prend donc toujours au moins un paramètre, ce texte à transformer. Il peut en prendre d'autres (reprenons l'exemple de la date : un second paramètre peut être le format).
public function getFilters()
{
return array(
'
date
'
=>
new Twig_Filter_Function('
date
'
),
'
datebis
'
=>
new Twig_Filter_Function($this
,
'
date
'
),
'
fluo
'
=>
new Twig_Filter_Function($this
,
'
fluo
'
),
);
}
public function date($timestamp
,
$format
)
{
return '
42
'
.
date($format
,
$timestamp
);
}
public function fluo($text
,
$what
)
{
return preg_replace('
/(
'
.
$what
.
'
)/
'
,
'
<span style="background-color: #FFFF00; color: #0000FF;">\1</span>
'
,
$text
);
}
Remarquez qu'on a ici surchargé le filtre standard de Twig date. On peut évidemment brancher derrière ce filtre une implémentation beaucoup plus spécifique en remplacement du fonctionnement standard. Un exemple d'implémentation de filtre tout à fait personnalisé est aussi fourni ci-dessus pour le filtre datebis, qui ne surcharge aucun filtre préexistant. Une implémentation de filtre doit renvoyer, au même titre qu'une fonction, ce qui doit être écrit dans le fichier renvoyé au navigateur. La dernière méthode, fluo, permet de mettre en évidence ce qui est passé en argument. Maintenant, comment appeler ces filtres ?
{{ '1305238362'|datebis(l jS \of F Y h:i:s A')}}
{{ 'Un peu de texte'|fluo('t')|raw }}
Remarquez l'utilisation du filtre raw après fluo : ce dernier renvoie du code HTML à écrire tel quel, il faut donc l'écrire sans aucun échappement.
V. Conclusion▲
Il n'est pas très compliqué d'écrire une extension pour Twig, ce n'est cependant pas une raison pour implémenter une extension pour tout besoin, n'oubliez surtout pas de regarder du côté des extensions déjà existantes (les officielles).
Il est évidemment possible d'aller beaucoup plus loin en utilisant ces quelques principes de base : on peut vouloir utiliser des dates dans une version plus verbeuse comme « il y a deux jours » au lieu de « 10/05/2011 ». On peut aussi, suite à une recherche, vouloir mettre les mots correspondant à la requête en emphase forte et en rouge. Ou bien la traduction : non, c'est un mauvais exemple, c'est déjà géré par une extension, même si elle est beaucoup plus compliquée que ce que l'on a fait ici, puisqu'elle gère de nouveaux jetons, une grammaire Twig étendue.
Merci à creativecwx, winzou, nathieb et djayp pour leur aide lors de la rédaction de cet article ! Merci à Claude Leloup pour sa précieuse relecture orthographique !