DEV Community

Cover image for I wrote twig in 50 lines of code
Simone Gentili
Simone Gentili

Posted on • Updated on

I wrote twig in 50 lines of code


Whenever I need to render a "twig like" template engine page, I need these components. The $page: the name of the template I want to render. $hierarchy of template: from the $page one, ... to te root template. Yes, because in a twig like template engine a template can extends another one a so on. $cache: because it is important to store cached file and keep contents from cache instead of reopen files any time. Last but not least $blocks: because each template can overwrite blocks of parent template.

$page = 'page';
$hierarchy = [];
$cache = [];
$blocks = [];
Enter fullscreen mode Exit fullscreen mode


Build the hierarchy of templates is very very simple. Just open desired page and check if extends part is present. Look for parent template until the template does not contains a parent.

Meanwhile I build hierarchy, I also store all template needed. I'll need again in next step to build the ultimate rendered template. Cache can help me to build better performances.

do {
    $hierarchy[] = $page;
    $filename = __DIR__ . '/../templates/'.$page.'.engine';
    $cache[$page] = file_get_contents($filename);
    $re = '/extends \'([\w]{0,})\'/m';
    preg_match_all($re, $cache[$page], $matches, PREG_SET_ORDER, 0);
    if ($matches != []) {
        $page = $matches[0][1];
} while ($matches != []);
Enter fullscreen mode Exit fullscreen mode


Now we have a hierarchy of template. I reverse $hierarchy of template to start from the root template. Stepping back from here to the desired template, I can check if some block must be rewritten.

foreach(array_reverse($hierarchy) as $template) {
    $re = '/ block ([\w<>\/ \n\s]{0,}) /m';
    $content = $cache[$template];
    preg_match_all($re, $content, $matches, PREG_SET_ORDER, 0);
    $blockList = array_column($matches, 1);
    foreach ($blockList as $block) {
        $re = '/{% block '.$block.' %}([\{\}\[\]\w=\"\<\/\> \n\.]{0,}){% endblock '.$block.' %}/m';
        preg_match_all($re, $content, $matches, PREG_SET_ORDER, 0);
        if ($matches != []) {
            $blocks[$block] = $matches[0][1];
Enter fullscreen mode Exit fullscreen mode

Now I've an array of block. Each block contains the ultimate version of each block.

Render blocks

Now I'll get the content of last item of the cache. Remember? Is the root template. And now, ... I'll rendere all the block using the array of "ultimate" blocks.

$content = end($cache);
foreach($blocks as $name => $block) {
    $re = '/{% block '.$name.' %}([\{\}\[\]\w=\"\<\/\> \n\.]{0,}){% endblock '.$name.' %}/m';
    $content = preg_replace($re, $blocks[$name], $content);
Enter fullscreen mode Exit fullscreen mode


Last implemented feature is the render of the model. Like in twig, ... {{foo}} render the foo variable.

$model = [ 'title' => 'titolo dal modello', ];

$re = '/{{([\w]{0,})}}/m';
preg_match_all($re, $content, $matches, PREG_SET_ORDER, 0);
foreach(array_unique(array_column($matches, 1)) as $var) {
    $content = str_replace('{{'.$var.'}}', $model[$var], $content);
Enter fullscreen mode Exit fullscreen mode

Render, ...

Now $content contain the rendered template. Just print it

echo $content;
Enter fullscreen mode Exit fullscreen mode


Ok, this is untested code. Is just an exercise I've made to play with regexp and to check if I was able to build a template engine like this. I am sure I'll never use it in production. Is jsut an exercise.


The link to the video I've made is here: I wrote twig in 50 lines of code. I this video I speak in italian but the code is in php.

Top comments (0)