PHP Template Parsing
I built, designed and try to maintain grimwar.com. It started in 2009 as a project for some buddies that play Magic the Gathering. It was never “planned” per se. Instead, I just started writing code to provide the features my friends wanted. Then other people started using it and now the site has close to 900 users.
I wrote Grimwar before I really understood object-oriented PHP and before I had any level of grasp on the MVC pattern. That being said, the code for Grimwar is pretty stable despite my PHP ineptitude and the organic way the site grew. I have decided to try to invest some time here and there in improving the site but before I can add features I really need to clean some stuff up. The biggest problem is that code and html are completely intertwined. Most pages on the site have different information for guests and logged-in users so that part of the code is pretty ugly.
So, last night I started working on a templating system that is customized for grimwar.com. One of the features of the site is the ability for users to select their own theme. There are a variety of themes that highlight some of the popular characters from the franchise. A templating system, in addition to separating design from code, could also offer themes with a lot more variety. Not that I plan to go there anytime soon…there is real work to be done!
Anyway, here’s my main template parsing method. I finished it around 12:30am last night and there are several parts that I will eventually change but it works pretty well for now. This assumes the programmer that implements the method knows what they are doing…which is usually a bad assumption. It needs a lot more error handling because its fails are currently less-than-graceful!
Template Chunk Example:
<div class='navigation'>
<ul>
{{main_nav}}
<li><a class="navlink" href="{{url}}" title="{{title}}">{{text}}</a></li>
{{/main_nav}}
</ul>
</div>
<h1>{{page_header}}</h1>
Data Array Example:
// example of loop variables $data["main_nav"][0]["url"] = "http://www.google.com"; $data["main_nav"][0]["title"] = "Test Link"; $data["main_nav"][0]["text"] = "TEST"; $data["main_nav"][1]["url"] = "http://www.msn.com"; $data["main_nav"][1]["title"] = "Test Link 2"; $data["main_nav"][1]["text"] = "TEST2"; // example of standard variable $data["page_header"] = "Home";
Parsing Method:
public static $template;
static function parse($templateName, $data, $minify = true)
{
// load the requested template as a string
self::$template = file_get_contents(SITE_PATH . "/templates/" . $templateName . ".html");
// match looping template variables
if(preg_match_all("/{{([^\/}]+)}}(.*){{\/\\1}}/ms", self::$template, $matches) !== false)
{
// readable names for match results
$entire_strings = $matches[0];
$loop_variables = $matches[1];
$loop_blocks = $matches[2];
// process each loop variable
for($i = 0; $i < count($loop_variables); $i++)
{
// set temporary values for this iteration
$entire_string = $entire_strings[$i];
$loop_variable = $loop_variables[$i];
$loop_block = $loop_blocks[$i];
// do we have data matching the loop variable name?
if(isset($data[$loop_variable]) && is_array($data[$loop_variable]))
{
// prepare a final find/replace pair for the template
$revised_string = "";
// replace each variable we have data for
for($j = 0; $j < count($data[$loop_variable]); $j++) { // create a new loop line for every iteration $loop_line = $loop_block; foreach($data[$loop_variable][$j] as $var_name => $replace_var)
{
$loop_line = str_replace("{{" . $var_name . "}}", $replace_var, $loop_line);
}
// append the completed line to a revised string
$revised_string .= $loop_line;
}
// replace the loop variables and splice into template
self::$template = str_replace($entire_string, $revised_string, self::$template);
}
// we don't have data...remove the string from the template
else
{
self::$template = str_replace($entire_string, "", self::$template);
}
}
}
// match other vars, no regex for processing speed
// simply replace all non-array data values that exist in template
foreach($data as $variable => $value)
{
if( ! is_array($value))
{
self::$template = str_replace("{{" . $variable . "}}", $value, self::$template);
}
}
// strip any vars we missed to prevent ugly output
self::$template = preg_replace("/{{[^}]+}}/", "", self::$template);
// convert all whitespace to a single space
if($minify)
{
self::$template = preg_replace("/\n|\t|\r/", " ", self::$template);
self::$template = preg_replace("/ +/", " ", self::$template);
}
// final template
return self::$template;
}