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;
}