Home > Software design >  Simple templating system with {{field}} in PHP
Simple templating system with {{field}} in PHP

Time:01-26

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?

CodePudding user response:

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.

CodePudding user response:

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

<?php
ob_start();
?>

Hello
{{field123}} and {{field4}}    
World
<?php // or require_once 'template1.php'; ?>

<?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.

  •  Tags:  
  • Related