Skip to content

Instantly share code, notes, and snippets.

@georgepsarakis
Last active December 28, 2015 11:59
Show Gist options
  • Save georgepsarakis/7497784 to your computer and use it in GitHub Desktop.
Save georgepsarakis/7497784 to your computer and use it in GitHub Desktop.
PHP Templating
<?php
class T {
protected $Context = array();
protected $Compiled = NULL;
public $Regex_Variable = '~\{\{\s+[a-z_0-9\|:\.]+\s+\}\}~imsu';
public $Separator_Filter = '|';
public $Separator_Directive = ':';
protected $HTML_Encoder = NULL;
protected $HTML_ENCODE = TRUE;
protected $VALUE_CACHE = array();
function __construct($template, $context) {
$this->Context = is_object($context) ? get_object_vars($context) : $context;
$this->Template = $template;
$this->set_html_encoder('html_encode_default');
if ( is_array($this->Context) )
array_walk_recursive($this->Context, array($this, 'array_context'));
}
protected function array_context(&$context, $key) {
if ( is_object($context) )
$context = get_object_vars($context);
}
public function set_html_encoder($function) {
if ( function_exists($function) ) {
$this->HTML_Encoder = $function;
} elseif ( method_exists($this, $function) ){
$this->HTML_Encoder = array($this, $function);
} else {
return FALSE;
}
return TRUE;
}
public function html_encode_default($html) {
return htmlspecialchars($html, ENT_COMPAT, 'UTF-8', FALSE);
}
protected function get_value($var){
if ( array_key_exists($var, $this->VALUE_CACHE) )
return $this->VALUE_CACHE[$var];
$filters = array();
if ( strpos($var, $this->Separator_Filter) !== FALSE ){
$filters = explode($this->Separator_Filter, $var);
$var = array_shift($filters);
}
$context = $this->Context;
if ( is_array($context) ){
$variable = $context;
foreach(explode('.', $var) as $p) {
if ( is_array($variable) && array_key_exists($p, $variable) ) {
$variable = $variable[$p];
} else {
$variable = "";
break;
}
}
} else {
$variable = $this->Context;
}
foreach($filters as $filter)
$variable = call_user_func($filter, $variable);
$value = $this->HTML_ENCODE ? call_user_func($this->HTML_Encoder, $variable) : $variable;
$this->VALUE_CACHE[$var] = $value;
return $value;
}
public function compile() {
$this->Compiled = preg_replace_callback(
$this->Regex_Variable,
array($this, 'replacer'),
$this->Template
);
}
public function get_variables() {
$matches = array();
preg_match_all($this->Regex_Variable, $this->Template, $matches);
if ( sizeof($matches) >= 1 ){
$matches = array_map(array($this, '_variable'), $matches[0]);
}
return $matches;
}
protected function _variable($v) {
return trim($v, '{ }');
}
protected function replacer($match) {
$var = $this->_variable($match[0]);
return $this->get_value($var);
}
public function __toString() {
if ( is_null($this->Compiled) )
$this->compile();
return $this->Compiled;
}
}
$tmpl = "<div>Some <p>HTML</p> that contains {{ n }} variables and {{ var_1 }}.</div>\n";
/* Passing an array as Context */
$T = new T('Array Example: ' . $tmpl, array('n' => 2, 'var_1' => 'a <b>sample</b> string &amp; some HTML'));
print_r($T->get_variables());
/* the result is passed through htmlspecialchars and thus is HTML encoded */
print $T; // or (string)$T
/* Passing a simple string. All variables will be replaced with this value. */
$T = new T('String Example: ' . $tmpl, 'VARIABLE');
print $T;
/* Passing Object Instance as Context */
$context = new stdClass;
$context->n = 2;
$context->var_1 = 'a second sample <span class="highlight">string</span>';
$T = new T('Object Example: ' . $tmpl, $context);
print $T;
/* Using filters */
$tmpl = "<div>Some <p>HTML</p> that contains {{ n }} variables and {{ var_1|md5|strlen }}.</div>\n";
$T = new T('Filter Example: ' . $tmpl, array('n' => 2, 'var_1' => 'a <b>sample</b> string &amp; some HTML'));
print $T;
/* Nested arrays */
$tmpl = "<div>Some <p>HTML</p> that contains {{ n.m }} variables and {{ a.b|strlen }}.</div>\n";
$T = new T('Nested Arrays Example: ' . $tmpl, array('n' => array( 'm' => 2), 'a' => array('b' => 'a <b>sample</b> string &amp; some HTML')));
print $T;
/*
Simple benchmark.
Get a rough estimate of the required time to parse the template.
Probably a test with more variables should be included.
*/
$tmpl = str_repeat("<div>Some <p>HTML</p> that contains {{ n }} variables and {{ var_1 }}.</div>\n", 100);
print sprintf("Template Size: %d\n", strlen($tmpl));
$start = microtime(TRUE);
foreach(range(1, 1000) as $iteration){
$T = new T($tmpl, array('n' => 2, 'var_1' => 'a second sample string'));
$T->compile();
}
$benchmark = number_format(1000*(microtime(TRUE) - $start), 6);
print 'Simple Benchmark: ' . $benchmark . "μsec\n";
// ~700μsec per template compilation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment