Simple templating system with {{field}} in PHP

January 26, 2022, at 7:20 PM

I'm designing a simple templating system for a CMS in PHP which internally currently uses something like:

require_once 'templates/template1.php`;

to import the desired template.

I would like every content {{field123}} in this PHP file to be automatically converted into <?php echo $row['field123']; ?> before being passed into require_once and executed by PHP.

Is there a way to activate a preprocessor (I know that PHP is already named after preprocessor) that does this replacement {{anything}} -> <?php echo $row['anything']; ?> before executing the PHP code template1.php? If not, what's the usual way to do this?

Answer 1

Having PHP code in templates - especially code with potential side-effects - can get dirty real quick. I would recommend using static templates, treating them as strings instead of executing them, then parsing them for tokens, with your main application compiling them and handling output.

Here is a rudimentary implementation that parses variables into tokens, and also handles mapped function calls in your templates. First, "fetching" our template (for a simple example):

$tpl = 'This is a sample template file. 
It can have values like {{foo}} and {{bar}}. 
It can also invoke mapped functions: 
{{func:hello}} or {{func:world}}. 
Hello user {{username}}. Have a good day!';

Then, the template parser:

function parse_template(string $tpl, array $vars): string {
    // Catch function tokens, handle if handler exists:
    $tpl = preg_replace_callback('~{{func:([a-z_]+)}}~', function($match) {
        $func = 'handler_' . $match[1];
        if(function_exists($func)) {
            return $func();
        return "!!!What is: {$match[1]}!!!";
    }, $tpl);
    // Generate tokens for your variable keys;
    $keys = array_map(fn($key) => '{{' . $key . '}}', array_keys($vars));
    // Substitute tokens:
    $tpl = str_replace($keys, $vars, $tpl);
    return $tpl;

These are our handler functions, with handler_X matching {{func:X}}.

function handler_hello() {
    return 'HELLO THERE';
function handler_world() {
    return '@Current World Population: ' . mt_rand();

Then, here are the variables you'd like to parse in:

$vars = [
    'foo' => 'Food',
    'bar' => 'Barnacle',
    'username' => 'Herbert'

Now let's parse our template:

$parsed = parse_template($tpl, $vars);
echo $parsed;

This results in:

This is a sample template file. 
It can have values like Food and Barnacle. 
It can also invoke mapped functions: 
HELLO THERE or @Current World Population: 1477098027. 
Hello user Herbert. Have a good day!

Job done. You really don't need a complicated templating engine for something like this. You could easily extend this to allow the handlers to receive arguments defined in the template tokens -- however I'll leave that for your homework part. This should do to demonstrate the concept.

Answer 2

As mentioned in a comment and in How do I capture PHP output into a variable?, the use of output buffering can work:

{{field123}} and {{field4}}    
<?php // or require_once 'template1.php'; ?>
$s = ob_get_clean();
$a = array('field123' => 'test', 'field4' => 'test2');
$s = preg_replace_callback('/{{(.*?)}}/', function ($m) use ($a) { return isset($a[$m[1]]) ? $a[$m[1]] : $m[0]; }, $s);
echo $s;
// Output:
// Hello
// test and test2    
// World

Here we also used a method similar to Replace with dynamic variable in preg_replace to do the replacement.

Rent Charter Buses Company
jQuery Toggle Class on &quot;a&quot; Link Hover

jQuery Toggle Class on "a" Link Hover

I have a custom cursor div which changes when hovering over various elementsSo far, I have the cursor expanding on hover of

Increase Bidding Rate On Assests In Opensea-js

Increase Bidding Rate On Assests In Opensea-js

everyone so I have been working on a Bidding Bot to make Offers on Assests of a Collection on Openseaio

Batch job got 1205 &quot;Lock wait timeout exceeded&quot; error

Batch job got 1205 "Lock wait timeout exceeded" error

I have a batch with many jobs almost over 9,000 and the first of them consumed by workers always get 1205 "Lock wait timeout exceeded" errorsI have submitted an issue on GitHub but it was closed so I'm asking for help here