11

Overview

I'm currently writing a template engine. It even supports multiple "format"s. Currently it can parse .php files and .tpl (specific to this engine).

I'll give you a little example of both, just to give you an Idea.

template.php:

Name: <?php echo $this->h($name) ?>
Posts: 
<?php foreach($posts as $post): ?>
    - <?php echo $this->h($post->name) ?> (<?php echo count($post->comments) ?> comments)
      <?php echo $this->render('post/shortpost', array('post' => $post)) ?>
<?php endforeach ?>

This is basicly just a standard PHP.

template.tpl

Name: {>$name}
Posts: 
{foreach($posts as $post):}
    - {>$post->name} ({=count($post->comments)} comments)
      {=:render('post/shortpost', array('post' => $post))}
{endforeach}

This templating "language" simply gets translated into PHP above.

Comparission

eval()

Currently these template's are parsed using eval().

Pro

  • I don't have to change any code

Contra

  • when an error occurs in a template you only get a useless error message which doesn't tell you in which file the error occurs and sometimes the line number is even wrong.
  • Security? Template files are only need to be readable?
  • It's difficult to debug the code.
  • Code is harder to understand
  • more .. ?

stream wrappers and include()

I recently read about stream wrappers in php. You even could create your own. A other solution than eval would be to create a custom stream wrapper for every template "format" and use include to parse the template.

This has the following (potential) flaws:

Pro

  • may solve the problems with showing the wrong file/line-number in error messages (has anyone experiences with this?)
  • you could handle the template file exactly how to want it to be handled. Full control.

Contra

  • allow_url_(fopen|include) has to be on?
  • it is slow? (is eval() slow too?)
  • no gain in security. include does basically the same thing as eval.
  • more ... ?

EDIT: cached parsed files and include()

A third option would to to parse the template to PHP code and cache them (as suggested by @Jen-YaKovalev).

Pro

  • includes caching

Contra

  • if an error occurs while including the rendered template and an error occurs the error message doesn't point you to the correct file/eventually shows you the wrong line number.
  • You need an extra tmp/ directory to save the parsed files. You need write permissions for PHP/webserver. Would be more insecure because hackers would append some malicious code easier.

EDIT: stream filters and include('php://filter')

lately found the following php.net pages:

This would be an other possibility to solve this problem. Using include('php://filter/read=filtername/resource=file.php'), I could include a file which would first go through the filter filtername, before it gets executed.

Pro

  • doesn't need so much code as stream wrappers

Contra

  • not so much possibilities as with stream wrappers (caching?)
  • security?
  • speed?

Question

  • Have experiences using stream wrappers for parsing template files or similar?
  • Is there yet an other solution?
  • Are there more pro's and contras?
  • Which one would you recommend?
MarcDefiant
  • 6,649
  • 6
  • 29
  • 49
  • 2
    I think, that the best approach here are cached templates. You basically generate php files and execute them. It would also improve the performance and scalability of your application. – Jen-Ya Oct 12 '12 at 09:10
  • There are 100's of cons to eval, I mean imagine gthe user is able to echo a PHP string into your template before you eval it due to using different buffers. Normally people would use regex and other parsing functions here to run dynamically for certain clauses, it is slow but it is better than stream wrappers, marginally. – Sammaye Oct 12 '12 at 09:12
  • @Jen-YaKovalev i can also cache the parsed templates using eval() and include – MarcDefiant Oct 12 '12 at 09:12
  • @Sammaye include does nearly excactly the same as eval(). `include "data:image/png;base64," . base64("")` – MarcDefiant Oct 12 '12 at 09:15
  • @Mogria It can, but then that's also a security flaw to include images within PHP in that manner, especially if they come from external sources due to the gif exploit where are valid image could have a piece of arbitery PHP code designed to run some specific function on your system, of course it's not just gifs. – Sammaye Oct 12 '12 at 09:24
  • I edited the question and added some pro's and contras and the suggested third option. I also did some reformatting ;-) – MarcDefiant Oct 12 '12 at 11:27
  • Just curious. Any reason why you chose to rebuild what Smarty accomplished a decade ago?? I've been using it since it's inception and I believe it does everything you're trying to accomplish and much much more. http://www.smarty.net/ – Bryan Allo Oct 17 '12 at 15:57
  • 1
    @BryanAllo Simply because I can. I do this education and fun. This is also part of a framework on which I'm currently coding. I don't use any other template engine, because I can do it myself, I want to do it myself and I also have plenty time. I also have a dependency less. – MarcDefiant Oct 17 '12 at 18:00
  • In that case, I second Jen-Ya Kovalev. Cached templates solves your problems. That's essentially what Smarty does so it should work for you as well. Have fun. – Bryan Allo Oct 17 '12 at 18:04
  • @BryanAllo This has 1 mayor disadvantage: the cache files need to be writable so hackers could simply append malicious code. I also doesn't fix the problem with the error message pointing to the wrong file/line. – MarcDefiant Oct 17 '12 at 19:27
  • @Mogria How do hackers add malicious code? Unless they're authoring the templates themselves ... – Ja͢ck Oct 22 '12 at 05:51
  • @Jack Lets say we have the file `template.tpl`. A function now parses this template file and converts it to PHP code and stores the generated template in `template.php`. The problem is `template.php` needs to be writable and the code in it gets executed when the template is rendered the next time. Currently i don't have a file which needs to be writable, because I'm using eval(). – MarcDefiant Oct 22 '12 at 09:23

2 Answers2

1

I think it's just a taste of one's coding-style, you'd better vote it or something.

  • I personnaly think eval is evil (in every language),
  • had bad experiences with include + php wrappers (even integrated ones*),
  • knowing all big(gish) template systems use compiling to a php file (smarty, twig), this it the one, i would use.

(*) In an earlier project we used a 1-line code (an empty class-extension) in a data-url wrapped include, and its performance was awful.

pozs
  • 34,608
  • 5
  • 57
  • 63
  • what bad experiences you've has with stream wrappers? (what are integrated ones?). Would be nice if you could be more specific. – MarcDefiant Oct 22 '12 at 19:29
  • The Question is about a custom stream wrapper, not about data urls (see http://fr2.php.net/manual/en/function.stream-wrapper-register.php) – MarcDefiant Oct 23 '12 at 05:51
  • I know that, but i think i need to say it again. My question wasn't about data urls, it was about **custom** stream wrappers. So you basicly write a class like that one (http://fr2.php.net/manual/en/stream.streamwrapper.example-1.php) and register it as a stream wrapper using http://fr2.php.net/manual/en/function.stream-wrapper-register.php to the "protocol" `var://`. If now an URL begins with `var://` the registered class is called by the `fopen()`, `fread()` etc. functions. I could do the same to parse my templates. And the question is would that make sense? – MarcDefiant Oct 23 '12 at 11:19
  • As i wrote, all of your options would make sense, but as i experienced, stream wrappers (custom & built-in ones) *could* be slow, and i would prefer compiled / parsed (and that's why cached) files, with a simple include. – pozs Oct 23 '12 at 11:25
0

You certainly don't want to parse templates at every request on the production environment, it would be a waste of resources and consequently a slow and not a very smart approach, so I'd strongly suggest going with the cached parsed files and include() approach.

Felipe Ribeiro
  • 354
  • 1
  • 5