⬆️ ⬇️

Simple plugin for Twig or expand constants.

Twig is an excellent templating engine and, unlike the others I have come across, I like it more and more over time. Twig has many merits and one of them is extensibility.



Some time I quietly ruined my life a small problem, which was too lazy to waste time. Recently, I still forced myself and, I think, its solution would be well suited for a small article about plug-ins in Twig.



The problem itself is in constants inside the templates. There are such tasks when it is necessary to sew up some identifiers in the template. Numbers to arrange them is not very good, and if there are also constants for them, it’s a sin not to use the constant function. But the fact is that after compiling from a template, it is still calculated in runtime.

')

And what can we do? We are killing or renaming a constant in the wake of refactoring, and forget about the template. And IDE forgets, even the praised PHPStorm. Successfully compile before deploy all our mountain of templates, scatter on the server. Nothing fell, everything just doesn't work very well, and a huge sheet of identical Vorning falls on our head. Poorly? Disgusting!



Decision? Rezolvit constants in the process of compiling the template, the missing - swear.



Those who are not familiar with Twig or are not very well acquainted, tell (very briefly) that each template is parsed by plugins (even the basic features are implemented in the template engine using plugins), processed and compiled into a php class, which is then twitched in the display method. For example, let's take this template code, just with our constant:



 {% if usertype == constant('Users::TYPE_TROLL') %} ,  ! {% else %} ! {% endif %} 


The template will understand a relatively large tree of objects.



Here is a bit shortened, but still a big output print_r of the presentation of our template
 [body] => Twig_Node_Body Object ( [nodes:protected] => Array ( [0] => Twig_Node_If Object ( [nodes:protected] => Array ( [tests] => Twig_Node Object ( [nodes:protected] => Array ( [0] => Twig_Node_Expression_Binary_Equal Object ( [nodes:protected] => Array ( [left] => Twig_Node_Expression_Name Object ( [attributes:protected] => Array ( [name] => usertype ) ) [right] => Twig_Node_Expression_Function Object ( [nodes:protected] => Array ( [arguments] => Twig_Node Object ( [nodes:protected] => Array ( [0] => Twig_Node_Expression_Constant Object ( [attributes:protected] => Array ( [value] => Users::TYPE_TROLL ) ) ) ) ) [attributes:protected] => Array ( [name] => constant ) ) ) ) [1] => Twig_Node_Text Object ( [attributes:protected] => Array ( [data] => ,  ! ) ) ) ) [else] => Twig_Node_Text Object ( [attributes:protected] => Array ( [data] => ! ) ) ) ) ) ) 


It is additionally processed [here we need to wedge in] and as a result, it is compiled into such a file (also a slightly shorter version):



 class __TwigTemplate_long_long_hash extends Twig_Template { protected function doDisplay(array $context, array $blocks = array()) { if (((isset($context["usertype"]) ? $context["usertype"] : null) == twig_constant("Users::TYPE_TROLL"))) { echo ",  !"; } else { echo "!"; } } } 


$context here is what got into a bunch of input variables for this pattern. I hope everything is clear and no need to explain. The twig_constant function twig_constant almost the same as the standard constant and resolved in runtime.



To look at the problem with our own eyes, we remove the constant from the code and on the render we catch:

PHP Warning: constant(): Couldn't find constant Users::TYPE_TROLL in vendor/twig/twig/lib/Twig/Extension/Core.php on line 1387



It is the twig_constant call in the compiled version that we need to replace with the value of the constant.



For extensions, the Twig_Extension class is provided in the template engine, from which we inherit our extension. An extension can provide the template engine with sets of functions, filters, and other nonsense that you can think of through special methods that you can find yourself in the Twig_ExtensionInterface interface. We are interested in the getNodeVisitors method, which returns an array of objects through which all elements of the template tree will be passed before compiling it.



 class Template_Extensions_ConstEvaluator extends Twig_Extension { public function getNodeVisitors() { return [ new Template_Extensions_NodeVisitor_ConstEvaluator() ]; } public function getName() { return 'const_evaluator'; } } 


Before compiling, we just need to go through all the nodes, find among them the constant function with the usual text argument and change it to its value, or swear that there is no such constant.



This is how our node visitor turns out:



 class Template_Extensions_NodeVisitor_ConstEvaluator implements Twig_NodeVisitorInterface { public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) { //  -   constant  1  if ($node instanceof Twig_Node_Expression_Function && 'constant' === $node->getAttribute('name') && 1 === $node->count() ) { //    $args = $node->getNode('arguments'); if ($args instanceof Twig_Node && 1 === $args->count() ) { $constNode = $args->getNode(0); // 1   if ($constNode instanceof Twig_Node_Expression_Constant && null !== $value = $constNode->getAttribute('value') ) { if (null === $constantEvaluated = constant($value)) { //     -  throw new Twig_Error( sprintf( "Can't evaluate constant('%s')", $value ) ); } //  ,        //       :] return new Twig_Node_Expression_Constant($constantEvaluated, $node->getLine()); } } } //  ,  ,  ,     return $node; } public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) { return $node; } } 


That's how we replaced almost a third of our template tree with its usual value.



Just in case, I'll show you what happened in the compiled version.
 class __TwigTemplate_long_long_hash extends Twig_Template { protected function doDisplay(array $context, array $blocks = array()) { if (((isset($context["usertype"]) ? $context["usertype"] : null) == 2)) { echo ",  !"; } else { echo "!"; } } } 


Simple, interesting, useful.



Hopefully this will push someone to delve into Twig and try to expand it with something besides functions and filters. I am ready to listen to any criticism, and even answer questions in the morning. Discas!

Source: https://habr.com/ru/post/200614/



All Articles