Hi all,
I am writing a basic templating class for my own project. The basic usage is this:
$template = new Template('template_file.php');
$template->assignVariable('pageTitle', 'Home page');
$template->render();
Contents of 'template_file.php':
<?php print $pageTitle; ?>
This is what template class does step by step:
- Stores variables in a private array when assignVariable method is called
- When render method is called, extracts stored variables, includes template file in a
ob_start()
andob_end_clean()
block. Stores output in a variable withob_get_contents()
and then prints stored output.
I know this is a very simple templating class but works as expected. The question is should I delegate the including the template file to another class? I had this question when I was writing the unit tests for this class. I thought that file system interaction should be encapsulated. What do you think? If you think that it should not, how can I mock including a file in my tests?
Maybe I just pass the contents of the template file to the class like this:
$templateContent = file_get_contents('template_file.php');
$template = new Template($templateContent);
...
Edit: I decided to encapsulate the input process of template class for the sake of writing better unit tests and encapsulation. But as johannes pointed out, I needed to use eval()
for that purpose which seemed not right. Johannes pointed me to the direction of stream wrappers for mocking the including in unit tests. But that inspired a new idea on me. Here is what I am going to do; I will continue to use include()
in my template class but this time with stream wrappers. I will pass protocol handler to my template class while initializing it. This way I can create my own stream wrappers for fetching template data from database or using a local variable. Here are the examples:
$template = new Template('file://template_file.php');
stream_wrapper_register('database', 'My_Database_Stream');
$template = new Template('database://templates/3'); // templates table, row id 3
stream_wrapper_register('var', 'My_Var_Stream');
$myTemplate = '<?php print "Hello world!"; ?>';
$template = new Template('var://myTemplate');
I have already implement custom stream wrapper for local variables. Here it is:
class My_Var
{
protected $position;
protected $variable;
function stream_open($path, $mode, $options, &$openedPath) {
$url = parse_url($path);
global $$url['host'];
$this->variable = $$url['host'];
$this->position = 0;
return true;
}
public function stream_read($count) {
$ret = substr($this->variable, $this->position, $count);
$this->position = strlen($ret);
return $ret;
}
public function stream_eof() {
return $this->position >= strlen($this->variable);
}
}
stream_wrapper_register('var', 'My_Var');
$myvar = '<?php print "mert"; ?>';
include 'var://myvar';
exit;