
A simple plugin for Twig or deploy constants
Twig is an excellent template engine and, unlike the others that I have encountered, I like it more and more over time. Twig has many advantages, and one of them is extensibility.
For some time my life was quietly spoiled by a small problem, which laziness was wasting time on. Recently, I still forced myself and, I think, a solution to it would be well suited for a short article about plugins in Twig.
The problem itself is in the constants inside the templates. There are such tasks when it is necessary to stitch on some identifiers in a template. Placing them in numbers is not entirely good, but if there are constants for them, it’s a sin not to use the function
And what can we do? In the wake of refactoring, we kill or rename a constant, and forget about the template. And the IDE forgets, even the vaunted PHPStorm. We successfully compile before deploying our entire mountain of templates, scatter it on the server. Nothing fell, everything just doesn’t work very well, and a huge sheet of identical Vorings falls onto our heads. Poorly? Disgusting!
Decision? Resolve constants in the process of compiling the template, absent ones - swear.
For those who are not familiar with Twig or are not very familiar, we will tell (very briefly) that each template is parsed by plugins (even the basic features are implemented in the template engine using plugins), it is processed and compiled into a php class, which then twitches the method
The pattern will be sorted into a relatively large tree of objects.
It is additionally processed [we need to wedge here] and as a result it is compiled into such a file (also a slightly shortened version):
To see the problem with our own eyes, we remove the constant from the code and catch on the render:
It is the call
For extensions, the template engine provides a class
Before compiling, we just need to go through all the nodes, find the constant function with the usual text argument among them and change it to its value, or swear that there is no such constant.
This is how our node visitor turns out:
That's how we replaced almost a third of our template tree with the usual value.
Simple, interesting, useful.
Hopefully this will push someone to dig into Twig and try to expand it with something other than functions and filters. Ready to listen to any criticism, and even answer questions in the morning. Discas!
For some time my life was quietly spoiled by a small problem, which laziness was wasting time on. Recently, I still forced myself and, I think, a solution to it would be well suited for a short article about plugins in Twig.
The problem itself is in the constants inside the templates. There are such tasks when it is necessary to stitch on some identifiers in a template. Placing them in numbers is not entirely good, but if there are constants for them, it’s a sin not to use the function
constant
. But the fact is that after compilation from the template, it is still calculated in runtime.And what can we do? In the wake of refactoring, we kill or rename a constant, and forget about the template. And the IDE forgets, even the vaunted PHPStorm. We successfully compile before deploying our entire mountain of templates, scatter it on the server. Nothing fell, everything just doesn’t work very well, and a huge sheet of identical Vorings falls onto our heads. Poorly? Disgusting!
Decision? Resolve constants in the process of compiling the template, absent ones - swear.
For those who are not familiar with Twig or are not very familiar, we will tell (very briefly) that each template is parsed by plugins (even the basic features are implemented in the template engine using plugins), it is processed and compiled into a php class, which then twitches the method
display
. For example, take this template code, just with our constant:{% if usertype == constant('Users::TYPE_TROLL') %}
Давай, до свидания!
{% else %}
Привет!
{% endif %}
The pattern will be sorted into a relatively large tree of objects.
Here's a slightly shortened, but still great print_r output of our template view
[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 [we need to wedge here] and as a result it is compiled into such a file (also a slightly shortened 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 variables to the input of this template. I hope everything is clear and nothing needs to be explained. The function twig_constant
practically does not differ from the standard constant
and resolves in runtime. To see the problem with our own eyes, we remove the constant from the code and catch on the render:
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 call
twig_constant
in the compiled version that we need to replace with the value of the constant. For extensions, the template engine provides a class
Twig_Extension
from which we inherit our extension. The extension can provide the template engine with a set of functions, filters, and other nonsense that you can think of through special methods that you can find in the interface yourself Twig_ExtensionInterface
. We are interested in the methodgetNodeVisitors
, which returns an array of objects through which all elements of the parsed template tree will be passed before it is compiled.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 the constant function with the usual text argument among them 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 the 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 dig into Twig and try to expand it with something other than functions and filters. Ready to listen to any criticism, and even answer questions in the morning. Discas!