WP’s Backbone-like Templating Language

In yesterday’s post, I talked about fixing up old PHP code to be safer.

There’s another anti-pattern common in old PHP code, and that’s mixing the display logic with the output logic. While some of this is inevitable, nowadays, the rule is to use a templating system like Twig to separate out even small bits of HTML code from the logic.

WordPress does this on the front end via Underscore templates, but configured to use Handlebars-like syntax.

This is a PHP class that does the same thing with PHP. I wrote it so I could use the same, or similar, templates on both the client and server side.

You need to define FE_LAYOUTS to be the directory that contains the tempaltes, and FE_LAYOUTS_CACHE to be a writable directory where compiled templates are stored.

You can’t just drop this into your code because my directory structure was designed to organize shortcodes. Most applications need to organize based on a controller with several views. It can be hacked to do this.

It’s a little over 45 lines of code.

While templating isn’t strictly necessary, and in some situations, not desirable, they are really useful if your HTML is getting long and complex. It shortens the code in your views or controllers (and in legacy code, they tend to be one and the same), and doesn’t generally require a drastic change to your code.

<?php

namespace JK\FE;

/**
 * A templating system based on the wp.template syntax.
 * (Which is a variation on Underscore's templates.)
 *
 * {{ $dataescaped }}
 * {{{ $dataunescaped }}}
 * <# // logic #>
 *
 * Templates are compiled into PHP, then "included".
 * This uses extract() and include() so don't use
 * this on user-submitted data!  It's a security
 * risk!
 *
 * The logic and variable names should be
 * written PHP-style, not JS-style.
 *
 * All templates must be located in the templates/ directory.
 *
 */

class Template {
    public function __construct($tag) {
        $cachedir = FE_LAYOUTS_CACHE;
        $this->template = $template = LayoutFolders::findTemplate($tag);
        $this->compiled = $compiled = "$cachedir/$tag/$tag.inc";

        if (! file_exists($cachedir)) {
            mkdir($cachedir, 0777);
        }
        if (! file_exists("$cachedir/$tag/")) {
            mkdir("$cachedir/$tag/", 0777, true);
        }
        if (file_exists($compiled)) {
            if (stat($template)[8] > stat($compiled)[8]) {
                $this->compile();
            }
        } else {
            $this->compile();
        }
    }
    private function compile() {
        $search =  ['<#',    '#>', '{{{',         '}}}',  '{{',                           '}}'];
        $replace = ['<?php', '?>', '<?php echo(', ') ?>', '<?php echo(htmlspecialchars(', ')) ?>'];

        $t = file_get_contents($this->template);
        $t = str_replace($search, $replace, $t);
        file_put_contents($this->compiled, $t);
    }
    public function merge($data=null) {
        if ($data) extract($data);
        ob_start();
        include($this->compiled);
        return ob_get_clean();
    }
}

/**
 * Compiles $template, which is WP's Backbone template convention,
 * saving it into $compiled.
 */

Leave a Reply