Creating a basic template engine

Hello everybody,
I’ve started to work on building a template engine for a website since I need to seperate the logical units from the graphical units and template engines such as Smarty and TBS are way too big for what I really need: a very basic, simple template engine which manage simple tasks such as value reference and conditions.

The value referencing has been quite easy, a simple str_replace would do the job correctly. Where it gets complicated is at the conditional management. I need it to make, for exemple:

[IF USER_STATUS_ADMIN]{SHOW_ADMIN_LINK}[ELSEIF USER_STATUS_WRITER]{SHOW_WRITER_LINK}[ELSEIF USER_STATUS_TESTER]{SHOW_TESTER_LINK}[ELSE]{SHOW_ALL_LINK}[/IF]

turn into:


if $user_status_admin {
    echo $show_admin_link;
}elseif $user_status_writer{
    echo $show_writer_link;
}elseif $user_status_tester{
    echo $show_tester_link;
}else{
    echo $show_all_link;
}

I must admit that I am quite rusted in PHP, so to reach this goal I first thought about using a regex to dispatch all my values… I used this regex:

/\\[IF (.*?)\\](.*?)(\\[\\/IF\\]|\\[ELSEIF (.*?)\\](.*?)\\[\\/IF\\])/i

Before long I realized that I could only retrieve a single ELSEIF value. So I went with a string scaning method using this code:


$str = '[IF USER_STATUS_ADMIN]{SHOW_ADMIN_LINK}[ELSEIF USER_STATUS_WRITER]{SHOW_WRITER_LINK}[ELSEIF USER_STATUS_TESTER]{SHOW_TESTER_LINK}[ELSE]{SHOW_ALL_LINK}[/IF]';

$pos = strpos($str, '[IF ');

A new problem arose… Finding the first "[IF " was going fine, but finding the USER_STATUS_ADMIN between "[IF " and “]” was giving me trouble…

So here I am now, which method I should focus my energy on and how to achieve my goal.

Any help on the matter would be greatly appreciated,

Thanks ! :slight_smile:

I would probaly use Switch

Let the flame war begin.

PHP is a template engine. Learn braceless syntax.

Braceless syntax might be great for your own use, but not so much when many people are involved in the development of a project, including people for whom HTML is close to being scary and when you’re not alone to take decisions. The main goal was to seperate the business logic from the presentation logic, allowing everybody to work on parts of the website. The HTML is great as they see directly the output, by following strict standards and documenting their pages well, the goal can be achieved.

However, after discussing with my team for some time and explaining them that the more treatments we would have, the slower the website would run, they agreed to keep only the value as template. Which means, instead of allowing the [IF…ELSEIF…ENDIF] in the template, this whole part would be done directly in PHP file, so the template engine would only replace the values in {} by PHP-generated values. This allow everybody to continue working on the presentation logic by using {} for the values, which allows them to keep sight of the result.

To do so, I’ve made an extremely simple template engine which load the HTML file, replace the {} by a PHP variable then I output the result through an eval. Nothing fancy here, but it keeps the two logic well seperated and allow the team to work simultaneously. I was also thinking of adding a caching module to increase the overall performance.

What do you guys think of this solution ?

Thanks !

I suggest: look into Twig or [url=http://dwoo.org/]Dwoo.
Don’t try rolling your own, it’s a lot of work and others have already made excellent libraries for you :slight_smile:

This allow everybody to continue working on the presentation logic by using {} for the values, which allows them to keep sight of the result.

I was also thinking of adding a caching module to increase the overall performance.

Sorry to say it, but now it sounds like you are re-inventing Smarty to me :wink:

Your original post made me think that you could simply have a different sub-template for each of those use cases ‘$show_admin_link’ and so on and build up “the bits that stay the same” as include files.

But now I say that – even that is starting to sound like Smarty now, template inheritance – only had to use it once in anger, but I grudgingly admit, it is very clever.

incoming! …

Surely there is a lightweight Smarty clone out there you can use without having to design all this from the ground up?

Well so far the template module is coded and works fine… The code looks like


<?

class Template {

   public $template;

   function getFile($file) {
      $this->template = file_get_contents($file);
   }

   function set($tag, $content) {
      $this->template = str_replace("{".$tag."}", $content, $this->template);
   }

   function ouput() {
      eval("?>".$this->template."<?");
   }
}

?>

Just uncovered this old bookmark - a SP article no less - http://www.sitepoint.com/beyond-template-engine/ maybe there is something there which will help you.

What do you know? You’re trying to build a template engine within a template engine – even when such stupidity already exists in the form of smarty.

I’m not impressed.

And if you think the php syntax…


<?php if( condition ): ?>
<div>display me</div>
<?php elseif ( condition ): ?>
<div>second branch</div>
<?php else: ?>
<div>third branch</div>
<?php endif ?>

Is harder to read than your proposal…


{if condition }
<div>display me</div>
{elseif condition}
<div>second branch</div>
{else}
<div>third branch</div>
{endif}

You’re deluded. At least use smarty…


{if condition }
<div>display me</div>
{elseif condition}
<div>second branch</div>
{else}
<div>third branch</div>
{/if}

Rather than waste the next 6 months writing and debugging a parser ( And if you think an HTML parse can be written and fully tested in under that time you don’t know what you’re talking about at all ).

Anyway, In my experience anyone who can’t figure out PHP short syntax is going to be too stupid to figure out any other layer you might devise. That’s the primary reason smarty is a waste of time - it solves nothing. The stupid remain stupid and you’ve just added another syntax that has to be learned by the team to the project.

It doesn’t matter how you couch “if/then/else”, “for/each” and the like. Some people can’t deal with logic and don’t want to.

The main goal was to seperate the business logic from the presentation logic,

That can be done without resorting to a separate template engine.


public function parseTemplate($template, array $output = array() ) {
		
	// Extract the global output first.
	extract( $this->getArrayCopy() );
		
	// Start an output buffer to capture the results of the following include.
	ob_start();

	if (!include($template) {
		throw new Exception('Unable to read template file '.$template);	
	}
		
	// Now return that output.
	return ob_get_clean();
}

The object this belongs to extends ArrayObject. That means you can put your output data into it using array syntax. That data is extracted into local scope by this function, then the template file is included and runs in the scope of this function. That will provide separation of business and display logic you’re looking for.

You just called eval() (Which is a clear sign you’re going down a BAD, BAD path). So your “template” is nothing more than a php file with an alternate way of saying “<? ?>”. Furthermore, unlike a true PHP file if your template has inline javascript AT ALL it won’t work. And if the user wants to write text that includes {} for any reason they won’t be able to.

I’ll reiterate - Writing a parsing engine is not easy.

I must say that I am quite new at web programmation, been thrown in a project and I have to deal with the situation. I tried something, seemed to work so far but I could only test it on one page which was static. I don’t know why the eval is a bad thing nor why the javascript wouldn’t work…

As for your code,


public function parseTemplate($template, array $output = array() ) {
 
	// Extract the global output first.
	extract( $this->getArrayCopy() );
 
	// Start an output buffer to capture the results of the following include.
	ob_start();
 
	if (!include($template) {
		throw new Exception('Unable to read template file '.$template);	
	}
 
	// Now return that output.
	return ob_get_clean();
}

I must admit that you lost me there. I understand that you are opening a buffer then returning the value of the include (guess it would be easier to dig into the whole caching thing this way), but I don’t get the link between your data and your template. Would it be possible to provide an example ?

Thanks

If you must use Twig as already mentioned… forget about smarty. Twig is where it is at in terms of flexibility, and quality.

Off Topic:

Rereading my posts I’m testier than normal. My apologies - some personal issues going on

Eval is expensive in terms of CPU. Further, eval code can’t be cached by APC. Finally eval can cause signifcant security problems. It has it’s place, but it’s not something I’d recommend.

The reason your templates wouldn’t work with inline javascript is, well, javascript uses {} in it’s syntax - constantly. The str_replace above would cause the inline javascript to try to be eval’ed as PHP code - probably resulting in a crash.

Stripped of all extra bells and whistles, this is my template engine


class Template extends ArrayObject {
  public $template;

  protected $output;

  public function __construct( $path, $array = array() ) {
    parent::__construct( $array );
    $this->template = $path;
  }

  public function __toString() {
    if (is_null($this->output) ) {
      $this->output = $this->parseTemplate();
    }

    return $this->output;
  }

  protected function parseTemplate() {
 
    extract( $this->getArrayCopy() );
 
    ob_start();

     if (!include($template) {
      throw new Exception('Unable to read template file '.$template);	
    }
 
    return ob_get_clean();
  }
}

Now, outside this class, say we need to display a table from the database



// create the template
$resultTable = new Template('myTableTemplate.phtml');

// using pdo, fetch some data
$s = $pdo->query("SELECT displayname, username, phone FROM users");
$resultTable['users'] = $s->fetchAll( PDO::FETCH_ASSOC );


Meanwhile, let’s look at myTableTemplate.phtml


<table>
  <thead>
    <tr>
      <th>Display Name</th>
      <th>User Name</th>
      <th>Phone</th>
    </tr>
  </thead>
  <tbody>
  <?php if(count($users) == 0): ?>
    <tr>
      <td colspan="3" class="empty">No results</td>
    </tr>
  <?php else: foreach($users as $user ): ?>
    <tr>
      <td><?= $user['displayname'] ?></td>
      <td><?= $user['username'] ?></td>
      <td><?= $user['phone'] ?></td>
    </tr>
  <?php endif ?>
  </tbody>
</table>

What extract() does is it maps the contents of the template to the local scope. Hence $resultTable[‘users’] becomes $users inside the template.

Continuing on with our example controller code above, displaying a table by itself isn’t a full html response. This is why the __toString method was used on my templates to return their contents – it allows simply, powerful, and effective template nesting.


$response = new Template('index.phtml', array( 'users' => $resultTable ));
echo $response;
exit;

The index.phtml template is…


<html>
  <body><h1>My users are</h1>
  <?= $users ?>
</html>

Since the two templates have different scopes the repeat of ‘users’ as a variable isn’t a problem.

Now, all that said, it’s just a starting point. My actual template system has additional methods for assembling common html objects, negotiating the http connection correctly, compressing the output, caching it, finding templates within the template system without requiring the programmer to type in the full path, and finally allowing project specific templates to pre-empt system templates at include time. Hopefully I’ll be able to release it soon as part of it’s larger package.

One testament to this system’s flexibility is that despite how much I hate smarty (and I really hate it), I can use this within smarty on my legacy projects. Again, the __toString method is the key - when smarty template engine asks a template object for it’s string representation it happily returns the html string.

Twig is cow manure to smarty’s horse manure. Neither is pleasant - both suffer from the same brain dead starting assumption that PHP is somehow unable to parse HTML or that designers who can’t grok short tags can somehow grok another markup entirely.

Thanks a lot ! I will work on all this :slight_smile:

Don’t take all the horse manuer advise here to heart either. First understand the uses and purposes of template engines. They are not for every project, and it’s not because someone can’t learn PHP. There are plenty of engines choose from, you probably don’t need to reinvent one.