For a group project I am trying to create a template engine for PHP for the people less experienced with the language can use tags like {name} in their HTML and the PHP will
Smarty :) ...
php:
$smarty->assign("people",$peopleArray)
smarty template:
{foreach $people as $person}
<b>{$person.name}</b> {$person.surname}<br />
{/foreach}
Couple other things to do but that's what smarty will be like essentially.
A simple approach is to convert the template into PHP and run it.
$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
For example, this converts the template tags to embedded PHP tags.
You'll see that I made references to $this->showVariable()
, $this->data
, $this->wrap()
and $this->unwrap()
. That's what I'm going to implement.
The showVariable
function shows the variable's content. wrap
and unwrap
is called on each iteration to provide closure.
Here is my implementation:
class TemplateEngine {
function showVariable($name) {
if (isset($this->data[$name])) {
echo $this->data[$name];
} else {
echo '{' . $name . '}';
}
}
function wrap($element) {
$this->stack[] = $this->data;
foreach ($element as $k => $v) {
$this->data[$k] = $v;
}
}
function unwrap() {
$this->data = array_pop($this->stack);
}
function run() {
ob_start ();
eval (func_get_arg(0));
return ob_get_clean();
}
function process($template, $data) {
$this->data = $data;
$this->stack = array();
$template = str_replace('<', '<?php echo \'<\'; ?>', $template);
$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
$template = '?>' . $template;
return $this->run($template);
}
}
In wrap()
and unwrap()
function, I use a stack to keep track of current state of variables. Precisely, wrap($ELEMENT)
saves the current data to the stack, and then add the variables inside $ELEMENT
into current data, and unwrap()
restores the data from the stack back.
For extra security, I added this extra bit to replace <
with PHP echos:
$template = str_replace('<', '<?php echo \'<\'; ?>', $template);
Basically to prevent any kind of injecting PHP codes directly, either <?
, <%
, or <script language="php">
.
Usage is something like this:
$engine = new TemplateEngine();
echo $engine->process($template, $data);
This isn't the best method, but it is one way it could be done.
Use Smarty.
If I'm not worried about caching or other advanced topics that would push me to an established template engine like smarty, I find that PHP itself is a great template engine. Just set variables in a script like normal and then include your template file
$name = 'Eric';
$locations = array('Germany', 'Panama', 'China');
include('templates/main.template.php');
main.tempate.php uses an alternative php tag syntax that is pretty easy for non php people to use, just tell them to ignore anything wrapped in a php tag :)
<h2>Your name is <?php echo $name; ?></h2>
<?php if(!empty($locations)): ?>
<ol>
<?php foreach($locations as $location): ?>
<li><?php echo $location; ?></li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
<p> ... page continues ... </p>
I had a very basic answer to something KINDA like this back before I started using DOMXPath.
class is something like this (not sure if it works quite like what you want but food for thought as it works very simple
<?php
class template{
private $template;
function __CONSTRUCT($template)
{
//load a template
$this->template = file_get_contents($template);
}
function __DESTRUCT()
{
//echo it on object destruction
echo $this->template;
}
function set($element,$data)
{
//replace the element formatted however you like with whatever data
$this->template = str_replace("[".$element."]",$data,$this->template);
}
}
?>
with this class you would just create the object with whatever template you wanted and use the set function to place all your data.
simple loops after the object is created can probably accomplish your goal.
good luck
Ok firstly let me explain something tell you that PHP IS A TEMPLATE PARSER.
Doing what your doing is like creating a template parser from a template parser, pointless and to be quite frank it iterates me that template parser's such as smarty have become so well at a pointless task.
What you should be doing is creating a template helper, not a parser as there redundant, in programming terms a template file is referred to as a view and one of the reasons they was given a particular name is that people would know there separate from Models, Domain Logic etc
What you should be doing is finding a way to encapsulate all your view data within your views themselves.
An example of this is using 2 classes
The functionality of the template class is for the Domain Logic to set data to the view and process it.
Here's a quick example:
class Template
{
private $_tpl_data = array();
public function __set($key,$data)
{
$this->_tpl_data[$key] = $data;
}
public function display($template,$display = true)
{
$Scope = new TemplateScope($template,$this->_tpl_data); //Inject into the view
if($display === true)
{
$Scope->Display();
exit;
}
return $Scope;
}
}
This is extreamly basic stuff that you could extend, oko so about the Scope, This is basically a class where your views compile within the interpreter, this will allow you to have access to methods within the TemplateScope class but not outside the scope class, i.e the name.
class TemplateScope
{
private $__data = array();
private $compiled;
public function __construct($template,$data)
{
$this->__data = $data;
if(file_exists($template))
{
ob_start();
require_once $template;
$this->compiled = ob_get_contents();
ob_end_clean();
}
}
public function __get($key)
{
return isset($this->__data[$key]) ? $this->__data[$key] : null;
}
public function _Display()
{
if($this->compiled !== null)
{
return $this->compiled;
}
}
public function bold($string)
{
return sprintf("<strong>%s</strong>",$string);
}
public function _include($file)
{
require_once $file; // :)
}
}
This is only basic and not working but the concept is there, Heres a usage example:
$Template = new Template();
$Template->number = 1;
$Template->strings = "Hello World";
$Template->arrays = array(1,2,3,4)
$Template->resource = mysql_query("SELECT 1");
$Template->objects = new stdClass();
$Template->objects->depth - new stdClass();
$Template->display("index.php");
and within template you would use traditional php like so:
<?php $this->_include("header.php") ?>
<ul>
<?php foreach($this->arrays as $a): ?>
<li><?php echo $this->bold($a) ?></li>
<?php endforeach; ?>
</ul>
This also allows you to have includes within templates that still have the $this
keyword access to then include themselves, sort of recursion (but its not).
Then, don't pro-grammatically create a cache as there is nothing to be cached, you should use memcached
which stores pre compiled source code within the memory skipping a large portion of compile / interpret time