views:

72

answers:

4

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:

  1. Stores variables in a private array when assignVariable method is called
  2. When render method is called, extracts stored variables, includes template file in a ob_start() and ob_end_clean() block. Stores output in a variable with ob_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;
A: 

By passing the contents using file_get_contents() and such you have to use eval() for execution which is bad in multiple ways. One of the most relevant here is that an opcode cache can't cache the file then. Doing an include('template_file.php'); let's APC or others cache the compiled script.

johannes
Yes, but when using include function, i need a dummy template file within my test folder. Also shouldn't I be testing only the templating class? Also how will I mock the include function?
matte
You might use a custom stream wrapper, or the data provider for sending mocked data.http://php.net/stream_wrapper_registerhttp://php.net/manual/en/wrappers.data.php
johannes
Hi Johannes, thanks! You have inspired a new idea on me. I have edited the question and added the solution i came up with.
matte
A: 

Irrespective of the evils of eval vs using a include, to answer your question, I'd have to agree and use a separate class to encapsulate the I/O aspect of your solution.

Whilst this might seem like overkill (as it will be in most instances), this is probably the only sane way to provide the isolation of control/dependency injection that you're after.

middaparka
A: 

The question is should I delegate the including the template file to another class?

The question is, why not?

just somebody
maybe overkill?
matte
only if "overkill" was your answer to unit tests. ;)
just somebody
not overkill ofcourse. I came up with a solution that totally encapsulates my template class. I have added the solution to my question.
matte
+1  A: 

I always liked the approach of this fellow:

http://www.massassi.com/php/articles/template_engines/

This approach takes advantage of the fact that PHP has started as a template engine. (The author also notes that it is silly to write a bloated template engine in PHP, when it is in fact itself a templating engine.) It might not really answer your question directly, but maybe it helps you.

dwergkees
My template class is very similar to his, actually same. But my question was that should i encapsulate including file? What if I need to parse a text retrieved from db?
matte
The method described in the above link is not a million miles away from how view scripts are processed in the Zend Framework. Views scripts are in the context of the parent view object, allowing you to make calls (which are then proxied to plugins generally) to helper methods. This is helpful if, for example, you need to do escaping (You can just do <?php echo $this->escape($this->var);).
berty