

Twig Smoke Rendering - Journey of Fails | Tomas Votruba
source link: https://tomasvotruba.com/blog/twig-smoke-rendering-journey-of-fails/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Twig Smoke Rendering - Journey of Fails
2022-06-20
In previous post, we explored the "whys" for Twig Smoke Rendering.
Today we will set on the journey towards this tool and mainly. Get ready for failure, demotivation, and despair. As with every new invention, the fuel can make us faster or burn us to death.
How did we define the goal of twig smoke rendering? We want to render any template and validate the code, and its context works. To start, let's look at this first simple homepage.twig
template:
{% include "snippet/menu.twig" %}
{% for item in items %}
{{ item }}
{% endfor %}
That's the goal. What is our starting point? The typical render we know is from a Symfony controller will process the template like this:
- include the
snippet/menu.twig
loaded in TWIG loaders - render it to HTML
- iterates the
$items
array and renders each item to string
1. Naive Render First
This journey will be very long, so we have to save as much energy as possible. Before we use the brain for thinking, let's approach the code naively. Maybe the most straightforward solution will work right from the start.
First, we prepare a minimal setup of the TWIG environment with a template loader:
use Twig\Environment;
use Twig\Loader\ArrayLoader;
// here we load the "homepage.twig" template and all the TWIG files in our project
$loader = new ArrayLoader(['homepage.twig']);
$twigEnvironment = new Environment($loader);
$twigEnvironment->render('homepage.twig');
We run the code... any guess what happens?
2. There is No Variable

First, we get an error on the non-existing $items
variable 🚫
Did we forget to provide it? There is an easy fix for that. We see the template is foreaching an array of strings. Let's pass some made-up value as 2nd parameter:
$twigEnvironment->render('homepage.twig', [
'items' => ['first', 'second'],
]);
We re-run... and it works!
Lure of Manual Thinking
We made a single little template to render correctly. At the same time, we also made a massive step back from any attempt to automate the process. We've just used our brain for static analysis:
- we looked into the code with our eyes,
- we deduce from the
for
tag called on$items
that the value is anarray
- we deduced from writing the
item
to the output that it is an array of strings.
It is correct, but how long will it take us for all 3214 variables in all our templates? 🚫
This solution is not generic, and without us, the CI would fail. The CI has to run without any intervention, the same way we raise an adult from our child. First, we can feed them manually, but in the long term, we should teach them how to use their hands, what food is, and how to get and eat it.
The render()
has to run generically without variables . How? Thanks to Alexandr for the rescue. Twig has an option to disable check for variable existence:
$twigEnvironment = new Twig\Environment($loader, [
'strict_variables' => false
]);
$twigEnvironment->render('homepage.twig');
Now we re-run the test... and it works precisely as we need to!
3. Without Variables, There is no Type

The variables are missing, but we can still render the file. That's fantastic!
Well, until we use a filter or function:
{{ login_name|length }}
The $login_name
is not there, but the filter/function still needs an argument 🚫.
Ironically, if we care about code quality and strict type declaration, it is even worse. Filter needs an argument of specific type. The filter expects a string
argument but gets null
—a fatal error 🚫
What can we do about it?
- Remove all filters and functions?
- Use regex to strip them away from the template, then render it?
That will turn into crazy regex depression, or we will remove too many templates from the analysis. Nothing will work.
At this moment, I'm seriously doomed. This great excellent idea to make an automated command is falling apart. We still have to provide all the variables in the templates.
There is this moment in every journey towards automation that hasn't been done before. The moment you stop and think - "Is this worth it? Is this even possible? Should I turn to manual work and accept the risk of a bug? Should I lick my wounds and give up?"

Let's Take a Break and Think Different
Hm, what if we could emulate something like the 'strict_variables'
option, just on another level. No idea how to do that.
"A big win is a summary of many small improvements."
Let's list what we already know and work with:
- We accept the filter/function must exist, and that's ok.
- We know it has to accept any number and types of arguments.
- We know they're just simple callbacks:
new TwigFunction('form_label', function ($value) {
// ...
});
4. Faking Tolerant Functions/Filters
Those callbacks are defined and tight to a filter/function name. If we know the filter name, we can override and make it tolerant to any input:
-return new TwigFunction('form_label', function ($value) {
+return new TwigFunction('form_label', function () {
// ...
});
Let's give it a try:
$environment->addFunction(new TwigFunction('form_label', function () {
return '';
}));
Hm, it has already defined the form_label
function... and crashes 🚫
Twig has an immutable extension design. Once it loads functions/filters, we cannot override it. I love this design because we know the join
function will be the same and never change. But how do we change an immutable object? 🚫

More despair is coming... is this all waste of time? Should we give up?
We got Beaten...

...but we're not dead.
Let's step back. What else can we do? The filter/function cannot be changed once loaded. Maybe we could fake custom twig extensions that would get loaded instead of the core ones?
But we would have to be responsible for manual work listing all the extensions, functions, and filters from the core - e.g., CoreExtension, FormExtension, etc. 🚫
There must be some better way.
The environment is locked and protected from change, but it must have been writable at the start. Otherwise, the TWIG would not have the core functions and filters. That means there must be some lock mechanism. Like entity manager from Doctrine has. If we can unlock entity manager, we can un this.... new plan is getting shape:
- we have to open the lock
- detect core filter/function names
- add them with tolerant closures
- that's it!
That's the basic plan. We tried to apply it in one project... and it worked! After 2 more days of struggle, we polished it to a working state. Now we can render a TWIG file with variables, functions, and filters, and it will pass!
5. Check for Existing Filters and Functions out of the Box
"When we find ourselves in times of troubles,
it is time to always look on the bright side of life."
This achievement moves us light years ahead. The rendering checks filters/functions by default. Variables don't have to exist, but filters are still run on them. That way, we will know 3 invalid states that can happen to filter/function:
- if template uses filter/function that does not exist we will know about it ✅
- if the filter exists in PHP code, but extensions are not loaded for missing tag, we will know about it ✅
- if the filter exists, the extension is loaded, but the array closure is missing, we will know about it ✅
return [
new TwigFunction('some_function', [$this, 'some_method']);
];
// ... no "some_method" found here
We're getting close, but it still does not run in CI 🚫
Will we make it to the glory, or will we give up and walk in shame? Stay tuned for the next episode to find out.
Happy coding!
Have you find this post useful? Do you want more?
Follow me on Twitter, RSS or support me on GitHub Sponsors.
Recommend
-
95
Rector is a PHP tool that handles 2 things: instant upgrades and architecture refactorings.What exactly Rector does and how does it work?
-
46
The standard is still behind the door, but feedback, before it gets accepted, is very important. After accepting it will be written down and it will be difficult to change anything.Try PSR-12 today and see, how it works for your code.
-
67
What's New in PHP 7.3 in 30 Seconds in Diffs 2 min Thu, Aug 16, 2018...
-
61
You already have a monorepo, you have at least 2 packages, autoloaded with composer and splitting works.Now you're about to set up testing and code quality tools.How to make testing so tight no bug can escape?
-
46
Affected versions Twig 1.0.0 to 1.37.1 and 2.0.0 to 2.6.2 are affected by this security issue. The issue has been fixed in Twig 1.38.0 and 2.7.0. This vulnerabili...
-
37
PHP Framework Trends Updated at 1. 12. What are yearly downloads of popular PHP frameworks from Packagist? Is it marketing fake news or is it true?
-
11
-
11
-
8
Easy and Quick way to Measure lines of Code in PHP 2023-08-20 The famous
-
7
How to Remove Transitional Dependencies You don't Need 2023-08-16 In the last post
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK