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.
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.
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
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.
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 β
— Tomas Votruba πΊπ¦ (@VotrubaT) June 23, 2022
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 π pic.twitter.com/5H8iXVNyGS
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.
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!
Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!