Search code examples
phptemplatespreprocessor

Simple templating system with {{field}} in PHP


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?


Solution

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