Simple php templating, keeping your views passive

For some projects you will need to render a page with data values, but not want to include an entire php templating library.

One of the methods you can use is to use php as a templating language itself:

<?php  $item = array('url' => 'projects', 'name' => 'Projects'); ?>
<li><a href="<?php echo $item['url']; ?>"><?php echo $item['name']; ?></a></li>

This is great and one of the most efficient way to render a page quickly. However the more you use this, you find your html and php becoming mixed together. The result is confusing to read, update and maintain.

The second problem with php templates is that you are encouraged to put logic in your views. This is bad because views should not know anything about the data they rely on. Views should be passive and your data should be mapped correctly to match it.

By implementing a template language you can separate your html template views from your php logic, your html templates can then be reused across any platform javascript, php, python etc

If you want a full templating solution I suggest looking at Twig for php. However this can also encourage you to put logic in your views. So for our example we will implement our own single line templating solution which will force you to render your view with the correct data!

Item.html
<li><a href="{{ item.url }}">{{ item.name }}</a></li>

View.php
$item = array('url' => 'projects', 'name' => 'Projects');
$url = 'Item.html';
echo preg_replace('/\{{ item.([^\{]{1,100}?)\}}/e', '$item[$1]', file_get_contents($url));

This uses RegEx to match all instances of {{ item. }} and replace them with the matching object data.

If we would like to use that for multiple pages we can create it into a reusable class:


Item.html
<li><a href="{{ item.url }}">{{ item.name }}</a></li>

View.php

class View {
public function render($model, $template) {
$html = '';
if (gettype($model) == 'array') { foreach ($model as $item) { $html .= $this->replace($item, $template); } }
else { $html .= $this->replace(array('data' => $model), $template); }
return $html;
}
    private function replace($item, $template) {
return preg_replace('/\{{ item.([^\{]{1,100}?)\}}/e', '$item[$1]', $template);
}
}

Load.php

class Loader {
private $templates = array();

public function load($name, $url) {
if (!$this->templates[$name]) { $this->templates[$name] = file_get_contents($url); }
return $this->templates[$name];
}
}


Main.php
$item = array('url' => 'projects', 'name' => 'Projects');


require('Loader.php');
require('View.php');


$loader = new Loader();
$template = $loader->load('Item', 'Item.html');

$view = new View();
$view->render($item, $loader->load($item, $template);

You can then extend these classes to render loops of templates or save modules and embed them inside other modules. I've created an example of nested module templates loading from csv configuration files at:
https://github.com/kmturley/php-simple-templates

No comments:

Post a Comment