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. I this video I speak in italian but the code is in php.
Top comments (0)