Twig Smoke Rendering - Fortune Favors the Bold

Found a typo? Edit me

In the previous post, we set on journey of fails with ups and downs. Well, mostly downs. I'm trying to be honest about the blind path process behind the final published work.

We made it through heaven and hell. Now we're rested and back to continue. Can we smoke render twig templates, or shall we give up?

After 5 challenging steps we made last time, we need to recharge our fuel. This episode will be more relaxing and more fun.

6. Way Too Tolerant Constants

Do you use constants in your TWIG files? Then you've come across this issue:

{{ constant("YourneyPath::WOODS") }}

This filter calls native constant() function.

Few years later, we rename the constant in the YourneyPath class:

 final class YourneyPath
-    const WOODS = 'woods;
+    const FOREST = 'forest;

We trust the IDE to handle renames for us, so we will not check any other files or templates.

Since PHP 8.0, we would get a Fatal error on missing constant, but PHP 7.4 and below is only warning 🚫

How do we make the constant() filter strictly on lower PHP versions?

In the same way, we made filters/functions input tolerant. We add a modified filter:

$constantFunction = new TwigFunction('constant', function ($constant) {
    if (defined($constant) === false) {
        throw new \Exception(sprintf('Constant "%s" not found', $constant));

    return constant($constant);

Now we carefully catch any missing constant on any TWIG and PHP version.

7. Where is the Form?

We have covered functions, filters, and variables. Now, most edge-cases depends on the specific features you use from TWIG.

In our case, there is one feature that will throw us a curve ball:

{% form_theme form "some_theme.twig" %}

What is this element?

It's pretty hard to Google, but in the end, we found out it's a "token parser". It's a way to parse TWIG syntax between {% ... %} to native PHP code. This token is called "form_theme" and sets a style theme only for the current form.

When we run this part of the code, we get an error:

The "form" variable is missing

Faking Token Parser?

We tried to replace token parser according to Twig Documentation, but failed.

In the end, we realized we use a $form variable as a name for any form. There was no other name used, and templates were perfectly isolated from each other. We come up with the simple solution of adding a simple form:

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

final class SimpleFormType extends AbstractType
    public function buildForm(FormBuilderInterface $formBuilder, array $options)
        $formBuilder->add('name', TextType::class);

Then we provide it the render method by default:

$simpleFormType = $this->formFactory->create(SimpleFormType::class);
$context['form'] = $simpleFormType->createView();

$environment->render($templateName, $context);

It feels a bit hacky, but it works, and we can control our form easily without pre-parsing the templates with regexes.

Fortune Favors the Bold

After 2 weeks of hard work, but mainly counter-intuitive thinking and countless mad experiments, we have a working tool that can smoke parse any of 200 template files we have in 2 seconds:

We've just added our latest toy to CI😋 with ✅

Tolerant #twig renderer 🎉🎉🎉

* covers all TWIG files
* dynamic rendering
* finds non-existing filters + functions + tags
* even wrong constants!
* blazing fast ⚡️
* no php-parser, no magic transform
* fun to make 😎

— Tomas Votruba 🇺🇦 (@VotrubaT) June 23, 2022

Based on this 3-post series, you can build your own tailored TWIG, Latte, or Blade Smoke Renderer. It might not be easy at the start, but if you make it, you'll enjoy sweet fruits.

Believe in Yourself

Next time there is a challenge that presents itself, it will look as impossible, crazy, and weird. Think about it differently, be naive, even crazy. Take on your journey; believe you can make it, and you will.

These experiences are priceless stories to share.

Happy coding!

Have you find this post useful? Do you want more?

Follow me on Twitter, RSS or support me on GitHub Sponsors.