Update- Thanks for all the responses. This Q is getting kind of messy, so I started a sequel if anyone's interested.
I was throwing together a quick script for a friend and stumbled across a really simple way of doing templating in PHP.
Basically, the idea is to parse the html document as a heredoc string, so variables inside of it will be expanded by PHP.
A passthrough function allows for expression evaluation and function and static method calls within the string:
function passthrough($s){return $s;}
$_="passthrough";
The code to parse the document inside a heredoc string is ridiculously simple:
$t=file_get_contents('my_template.html');
eval("\$r=<<<_END_OF_FILE_\n$t\_END_OF_FILE_;\n");
echo $r;
The only problem is, it uses eval
.
Questions
Can anyone think of a way to do this sort of templating without using
eval
, but without adding a parser or a ton of regex madness?Any suggestions for escaping stray dollar signs that don't belong to PHP variables without writing a full-on parser? Does the stray dollar sign problem render this approach not viable for 'serious' use?
Here's some sample templated HTML code.
<script>var _lang = {$_(json_encode($lang))};</script>
<script src='/blah.js'></script>
<link href='/blah.css' type='text/css' rel='stylesheet'>
<form class="inquiry" method="post" action="process.php" onsubmit="return validate(this)">
<div class="filter">
<h2>
{$lang['T_FILTER_TITLE']}
</h2>
<a href='#{$lang['T_FILTER_ALL']}' onclick='applyFilter();'>
{$lang['T_FILTER_ALL']}
</a>
{$filter_html}
</div>
<table class="inventory" id="inventory_table">
{$table_rows}
<tr class="static"><th colspan="{$_($cols+1)}">
{$lang['T_FORM_HELP']}
</th></tr>
{$form_fields}
<tr class="static">
<td id="validation" class="send" colspan="{$cols}"> </td>
<td colspan="1" class="send"><input type="submit" value="{$lang['T_SEND']}" /></td>
</tr>
</table>
</form>
Why use templating?
There's been some discussion of whether creating a templating layer is necessary in PHP, which, admittedly, is already pretty good at templating.
Some quick reasons templating is useful:
You can control it
If you preprocess the file before it goes to the interpreter, you have more control over it. You can inject stuff, lock down permissions, scrape for malicious php / javascript, cache it, run it through an xsl template, whatever.
Good MVC design
Templating promotes separation of view from model and controller.
When jumping in and out of
<?php ?>
tags in your view, it's easy to get lazy and do some database queries or perform some other server action. Using a method like the above, only one statement may be used per 'block' (no semicolons), so it's much more difficult to get caught in that trap.<?= ... ?>
have pretty much the same benefit, but...Short tags aren't always enabled
...and we want our app to run on various configurations.
When I initially hack a concept together it starts out as one php file. But before it grows I'm not happy unless all php files have only one <?php
at the beginning, and one ?>
at the end, and preferably all are classes except stuff like the controller, settings, image server, etc.
I don't want much PHP in my views at all, because designers become confused when dreamweaver or whatever poops the bed when it sees something like this:
<a href="<?php $img="$img_server/$row['pic'].png"; echo $img; ?>">
<img src="<?php echo $img; ?>" /></a>
This is hard enough for a programmer to look at. The average graphic designer won't go anywhere near it. Something like this is a much easier to cope with:
<a href="{$img}"><img src="{$img}" /></a>
The programmer kept his nasty code out of the html, and now the designer can work his design magic. Yay!
Quick update
Taking everyone's advice into consideration, I think preprocessing the files is the way to go, and the intermediate files should be as close as normal "php templating" as possible, with the templates being syntactic sugar. Eval still in place for now while I play with it. The heredoc thing has sort of changed its role. I'll write more later and try to respond to some of the answers, but for now...
<?php
class HereTemplate {
static $loops;
public function __construct () {
$loops=array();
}
public function passthrough ($v) { return $v; }
public function parse_markup ($markup, $no_escape=null, $vars=array()) {
extract($vars);
$eot='_EOT_'.rand(1,999999).'_EOT_';
$do='passthrough';
if (!$no_escape) $markup=preg_replace(
array(
'#{?{each.*(\$\w*).*(\$\w*).*(\$\w*).*}}?#',
'#{?{each.*(\$\w*).*(\$\w*).*}}?#',
'#{?{each}}?#',
'#{{#', '#}}#',
'#{_#', '#_}#',
),
array(
"<?php foreach (\\1 as \\2=>\\3) { ?>",
"<?php foreach (\\1 as \\2) { ?>",
"<?php } ?>",
"<?php echo <<<$eot\n{\$this->passthrough(", ")}\n$eot\n ?>",
"<?php ", " ?>",
),
$markup);
ob_start();
eval(" ?>$markup<?php ");
echo $markup;
return ob_get_clean();
}
public function parse_file ($file) {
// include $file;
return $this->parse_markup(file_get_contents($file));
}
}
// test stuff
$ht = new HereTemplate();
echo $ht->parse_file($argv[1]);
?>
...
<html>
{{each $_SERVER $key $value}
<div id="{{$key}}">
{{!print_r($value)}}
</div>
{each}}
</html>