Have you ever had the pleasure of reading PHP code with lines and lines of HTML jammed into strings and a ton of escape characters? I have. In fact, a lot of WordPress plugins are guilty of this. Perhaps you’re guilty of it yourself?
This article will show you how to separate your HTML markup from your PHP code using only a few simple lines of PHP — something I’ve been calling “micro templates.”
For this article, I rummaged through my ancient projects to find an authentically bad snippet of code. It didn’t take long to find. The following snippet I pulled from Uncultured.com, a custom blogging system I built nine years ago:
Snippet #1
<?php
function printMenu($menu_items) {
echo "
<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">
<tr>
<td class=\"borders\">
<table border=\"0\" cellpadding=\"3\" cellspacing=\"1\">";
for ($i = 0; $i < count($menu_items[0]); $i++) {
echo "
<tr>
<td class=\"buttons\">»</td>
<td class=\"buttons\"><a href=\"" . $menu_items[0][$i] . "\">" . $menu_items[1][$i] . "</a></td>
</tr>";
}
echo "
</table>
</td>
</tr>
</table>";
} // end printMenu
?>
The above PHP code isn’t too awful, but it does violate a best practice rule that I’ve adopted since: Avoid placing blocks of HTML in a string. Placing HTML in a string as demonstrated in the code above is unmanageable. All the double quotes need to be escaped, embedding variables is not very elegant, and it’s just ugly to read.
Of course, the alternative to echo’ing strings is simply to use PHP’s embed tags <?php ?> In fact, that’s what they were designed for! Using PHP embed tags, the above code becomes the following:
Snippet #2
<?php
function printMenu($menu_items) {
?>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="borders">
<table border="0" cellpadding="3" cellspacing="1">
<?php
for ($i = 0; $i < count($menu_items[0]); $i++) {
?>
<tr>
<td class="buttons">»</td>
<td class="buttons"><a href="<?php echo $menu_items[0][$i] ?>"><?php echo $menu_items[1][$i] ?></a></td>
</tr>
<?php
}
?>
</table>
</td>
</tr>
</table>
<?php
} // end printMenu
?>
Much better! Now, let’s say our requirements have changed and our printMenu
function is now a getMenu
function and needs to return the HTML as a string. My former self (from nine years ago) probably would have just replaced the echo
statements from the first snippet with a string concatenation, like this:
Snippet #3
<?php
function getMenu($menu_items) {
$out = "
<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">
<tr>
<td class=\"borders\">
<table border=\"0\" cellpadding=\"3\" cellspacing=\"1\">";
for ($i = 0; $i < count($menu_items[0]); $i++) {
$out .= "
<tr>
<td class=\"buttons\">»</td>
<td class=\"buttons\"><a href=\"" . $menu_items[0][$i] . "\">" . $menu_items[1][$i] . "</a></td>
</tr>";
}
$out =. "
</table>
</td>
</tr>
</table>";
return $out;
} // end printMenu
?>
Unfortunately, this has the same problems as the first snippet plus it suffers from the inefficiencies associated with string concatenation. From my limited experience with C programming, I know that every time a string is concatenated, a memory reallocation operation is executed which is a relatively expensive operation. Some programmers would do something even worse (I’ve seen it many times), building a string for each line of HTML, like this:
Snippet #4
<?php
function getMenu($menu_items) {
$out = "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n";
$out .= "<tr>\n<td class=\"borders\">\n";
$out .= "<table border=\"0\" cellpadding=\"3\" cellspacing=\"1\">\n";
for ($i = 0; $i < count($menu_items[0]); $i++) {
$out .= "<tr>\n<td class=\"buttons\">»</td>\n";
$out .= "<td class=\"buttons\"><a href=\"" . $menu_items[0][$i] . "\">" . $menu_items[1][$i] . "</a></td>\n";
$out .= "</tr>\n";
}
$out =. "</table>\n</td>\n</tr>\n</table>\n";
return $out;
} // end printMenu
?>
This code just gives me the urge to smack the developer who wrote it. They’re trying to “clean up” the code by adding even more string concatenations. This code is just awful. In addition to being a nightmare to read, with an increased number of string concatenations, it is horribly inefficient.
So, ideally we want to use embed tags <?php ?> (Snippet #2) and somehow return a string of the output. But how do we do that? Output buffering is the answer. And the surprising thing is that it is painfully simple. Basically, we just take Snippet #2 and add ob_start()
to the beginning of our function and ob_get_clean()
at the end.
Snippet #5
<?php
function getMenu($menu_items) {
ob_start();
?>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="borders">
<table border="0" cellpadding="3" cellspacing="1">
<?php
for ($i = 0; $i < count($menu_items[0]); $i++) {
?>
<tr>
<td class="buttons">»</td>
<td class="buttons"><a href="<?php echo $menu_items[0][$i] ?>"><?php echo $menu_items[1][$i] ?></a></td>
</tr>
<?php
}
?>
</table>
</td>
</tr>
</table>
<?php
return ob_get_clean();
} // end printMenu
?>
Adding ob_start()
starts the output buffer before any of the HTML in our function has been output. Now anything we try output (using echo or embed tags) gets stored in the output buffer. After we’ve captured all our output we can retrieve it from the output buffer using the ob_get_contents()
function or ob_get_clean()
which gets the buffer contents and clears the buffer.
Output buffering is also a handy way of creating “micro templates” in your PHP applications that don’t have the luxury of a full fledged template engine. The following function simply takes a file as an argument and returns a string containing the output generated by that file:
Micro Template Function
<?php
function get_template($file, $vars = array()) {
ob_start();
if (file_exists($file)) {
include($file);
}
return ob_get_clean();
}
?>
Because we’re including the template file within a function, variables from the caller are not available. The optional argument $vars
allows you to pass in an array of values from the caller for use in the template file.
I often use micro templates when developing WordPress plugins. They work great in these situations where one of the goals is to keep the overhead low. For large applications I would still recommend using a strong template system like Smarty.