I wrote twig in 50 lines of code
Tags: twig, symfony, php
Published: 2022-09-21 03:51:31

---
title: I wrote twig in 50 lines of code
published: true
description: Since I started to work with symfony and twig, I loved twig as template engine. Yesterday I published a live coding video (sorry but the spoken part is in only in italian) where I implement few twig features. In this post I'll show my implementation
tags: twig, symfony, php
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkq7pk7ip6gu9r8ronrr.jpg
series: php
---

## Components

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 = [];
```

## Hierarchy

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 != []);
```

## Blocks

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];
        }
    }
}
```

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);
}
```

## Variables

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);
}
```

## Render, ...

Now `$content` contain the rendered template. Just print it

```
echo $content;
```

## Conclusion

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.

## Link

The link to the video I've made is here: [I wrote twig in 50 lines of code](https://www.youtube.com/watch?v=_VaedksuXwM). I this video I speak in italian but the code is in php.