PHP Templates

Related Articles

As web sites become more and more complex, the importance of both content and code management becomes ever more significant. One of the more important aspects of managing a web site is the separation of the content of the web site from the scripts which make it dynamic. In the e-business world, this is known as separating the presentation logic from the application logic. In today's column I'll be examining this issue and discussing a few of the more popular solutions to it.

Since it's very possible that some may not know exactly what I am talking about when I say "presentation" or "application" logic, let me start off by illustrating the problem. Consider the following condensed example of an e-business website:

<HTML>

<HEAD>

<TITLE>The Coggeshall Shopping Co.</TITLE>

</HEAD>

<BODY>

<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0>

<TR>

<TD COLSPAN=2 ALIGN=CENTER>

<!--- Banner advertisement //-->

<?php

$dl = mysql_connect('localhost', 'username', 'password');

if($dl) {

$query = "SELECT count(banner_src) as t_banners FROM banners";

$result = mysql_query($query, $dl);

$value = mysql_fetch_row($result);

srand(1000000*microtime());

$query = "SELECT banner_src, href FROM banners WHERE id=".rand(1, $value['t_banners']);

$result = mysql_query($query, $dl);

$value = mysql_fetch_row($result);

mysql_close($dl); ?>

<A HREF="<?=$value['href']?>"><IMG SRC="<?=$value['banner_src']?>" BORDER=0></A>

<?php } // end of if($dl) ?>

</TD>

</TR>

<TR>

<TD>

<!-- This is the navigation links //-->

<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0>

<TR>

<TD><A HREF="products.php">My products</A></TD>

</TR>

<TR>

<TD><A HREF="support.php">Product Support</A></TD>

</TR>

<TR>

<TD><A HREF="contact.php">Contact webmaster</A></TD>

</TR>

</TABLE>

</TD>

<TD>

<!-- start of the actual content of the page //-->

<?php

// Assume $customer_name is set if we know the name of the customer

if(isset($customer_name)) {

echo "Hello, $customer_name:<BR><BR>";

}

?>

Welcome to the home page for the Coggeshall Product Co.

</TD>

</TR>

</TABLE>

<BR><BR>

<CENTER>

&copy; 2002 John Coggeshall, all rights reserved.<BR>

<A HREF="privacy.php">Privacy policy</A>

</CENTER>

</BODY>

</HTML>

Although the above page would be completely functional (assuming of course there was actually a table named banners full of banners, etc.) the design is very limiting. For example, what if I wanted to move all of my navigation links to the top of the page instead of on the side? Not only would I have to modify this page appropriately but also every single other page! On a site of any size (even a site with only four pages like this one) would be troublesome and lead to a lack of consistency in the feel of the web site. Furthermore, since the code to show our random banner is in-line with the content of the site the entire code-block must be cut-and-pasted anywhere a banner is desired. This can lead to further problems; a bug found in this code would have to be fixed everywhere a banner is found on the site. Finally, such a method is a major security risk! Since the script which is available online contains the username and password for your database, if the web server was to somehow end up dumping the script itself to the browser instead of parsing it security would be compromised.

The goal behind content and code separation is to maintain the manageability of the site. The look and feel of the web site should be defined completely separately from the PHP code which provides dynamic content. Furthermore, dynamic elements of a web site (such as a random banner from a database) should be encapsulated in an appropriate medium such as a function or class. For instance, the code which controls the random banner can be encapsulated into a function named get_banner_html()

<?php

function get_banner_html() {

$dl = @mysql_connect('localhost', 'username', 'password');

if(!$dl) return false;

$query = "SELECT count(banner_src) as t_banners FROM banners";

$result = mysql_query($query, $dl);

$value = mysql_fetch_row($result);

if(!$value) {

mysql_close($dl);

return false;

}

srand(1000000*microtime());

$query = "SELECT banner_src, href FROM banners WHERE id=" .

rand(1, $value['t_banners']);

$result = mysql_query($query, $dl);

$value = mysql_fetch_row($result);

if(!$value) {

mysql_close($dl);

return false;

}

mysql_close($dl);

$retval = <<< HTML

<A HREF="$value['href']"><IMG SRC="$value['banner_src']" BORDER=0></A>

HTML;

return $retval;

}

?>

Placing this code into a function of course offers many advantages. First and foremost to our discussion two lines of PHP code can now be used:

<?=get_banner_html()?>


Of course, this doesn't take into account any error that might occur generating the HTML for the random banner image. However since get_banner_html() returns a boolean false, a simple check can be used if desired:

<?php if($html = get_banner_html()) {

echo $html;

} else {

/* Code to deal with the error, if any */

}

?>

The second improvement that can be made to our example is the centralization of any elements that are common to every page (both HTML and any PHP code). This can be accomplished by placing common elements within their own file which is then imported into the pages using the include statement.

The next question to ask is how should the web page be divided? The answer to this question depends mainly on how much flexibility is desired. The rule of thumb on this subject is the more flexible a web site is, the harder it is to work with. In our example, one major improvement would be to separate the document into three separate segments: The header, content, and footer. The header would contain all of the PHP and HTML code from the start of the file to the opening of the table cell which contains the actual content for the page (example truncated for ease of reading):

... the rest of the HTML and PHP from the start of the page ...

</TR>

<TR>

<TD><A HREF="contact.php">Contact webmaster</A></TD>

</TR>

</TABLE>

</TD>

<TD>

<!-- start of the actual content of the page //-->

Likewise, all of the HTML and PHP code past the content of the page (the footer) can be stored in its own file:

</TD>

</TR>

</TABLE>

<BR><BR>

<CENTER>

&copy; 2002 John Coggeshall, all rights reserved.<BR>

<A HREF="privacy.php">Privacy policy</A>

</CENTER>

</BODY>

</HTML>

Assuming the header and footer content is stored in the files header.php and footer.php respectively, the page now becomes significantly easier to manage. Each segment can now be inserted into the actual page using the PHP include statement as show:

<?php

include("header.php");

// Assume $customer_name is set if we know the name of the customer

if(isset($customer_name)) {

echo "Hello, $customer_name:<BR><BR>";

}

?>

Welcome to the home page for the Coggeshall Product Co.

<?php include("footer.php"); ?>

This technique can also be applied to more than just presentation logic as well. Remember the get_banner_html() function we created earlier? This function can also be stored in its own file (along with any other functions) and included (we'll store it in a file called library.php). Since the header.php file we created needs the banner function, it is probably a good idea to include it there. This brings me to a common mistake that occurs with included PHP files. When dealing with includes which define things such as functions a little more care must be taken to ensure that your script does not attempt to define the same function twice. In some fashion, it is necessary to ensure that the library.php file is not included more than once. To facilitate this PHP offers the include_once statement. This statement is identical to the include statement, except it will only include the given file once per script execution.


Remember when I was mentioned the security issues regarding including a username and password in a publicly-available script? Now that this code has been moved into its own file, it can be stored in a directory the web server does not display to the public. Simply move the library.php file into such a directory and include it (this time specifying the complete path) in PHP. For example, if you stored library.php in /usr/local/php_include you would use the following code to include it:

include_once("/usr/local/php_include/library.php");

If you'd like to save yourself the effort of specifying the full path every time the include_path directive in the php.ini file (or the .htaccess file is using an Apache server). If this is done then only the filename itself library.php needs to be specified.

Although what I've discussed thus far is good practice for a PHP developer, sometimes there has to be an even greater separation of code and content. For example, if you are a member of a development team there may be graphic designers which have no programming experience at all. The ideal solution for this situation is to completely separate HTML code from the PHP code which controls it. In this way, graphic designers can stick to what they know without limiting your ability as a developer to provide dynamic content. To do this, you'll need to either develop or implement a third party template system. The concept behind a template system is to create a HTML document with placeholders which are then replaced when the page is loaded with actual content. This system can be quite complex or relatively system depending on your needs. For example, I have written a relatively simple template system which will replace placeholders denoted by %NAME% with either the content of another file (which can also be a template) or a PHP variable. This class (called quick_template) is found below:

class quick_template {

var $t_def;

function parse_template($subset = 'main') {

$noparse = false;

$temp_file = $this->t_def[$subset]['file'];

if(isset($temp_file)) {

$ext = substr($temp_file, strlen($temp_file)-6);

if(strcasecmp($ext, ".thtml") != 0) $noparse = true;

$fr = fopen($temp_file, 'r');

$content = (!$fr) ? "<!-- Error loading '$temp_file' //-->" :

fread($fr, filesize($temp_file));

fclose($fr);

} else {

if(isset($this->t_def[$subset]['content'])) {

$content = $this->t_def[$subset]['content'];

} else {

$content = "<!-- Content for '$subset' not defined //-->";

}

}

if(!$noparse)

$content=preg_replace("/%([A-Z]*)%/e",

"quick_template::parse_template(strtolower('$1'))",

$content);

return $content;

}

function quick_template($temp='') {

if(is_array($temp)) $this->t_def = $temp;

}

}

This class is used by defining an associative array whose keys match the placeholders in a template file. To help illustrate the use of this class, consider the following template file named mytemp.thtml:

<HTML>

<HEAD>

<TITLE>%TITLE%</TITLE>

</HEAD>

<BODY %BODYATTRIBS%>

%CONTENT%

</BODY>

</HTML>

Note: This template must be saved with a .thtml extension in order to be parsed for placeholders (otherwise it is simply passed straight through).

Now that we have defined a template, in order to replace these placeholders with actual values you would need to create an array with keys main, title, bodyattribs, date, and content. The value of each of these keys is then another array containing either a key called content or file indicating what the placeholder should be replaced with. Every array must contain at least a value for the main key, which specifies the root template (the one that parsing starts from). If the main template is under a different key name in your array you may specify a different root template by passing it's name to the parse_template() method.

$temp_vals = array('main'=> array('file' => 'mytemp.thtml'),

'title' => array('content' => 'Welcome to my web page! - %DATE%'),

'date' => array('content' => date('M jS, Y")),

'bodyattribs' => array('content'=> 'BGCOLOR=#FFFFFF'),

'content' => array('file' => 'maincontent.html'));

As shown above four placeholders have been defined with their appropriate values, two of which are actual content values. The other two placeholders define the main template file mytemp.thtml, and a normal non-parsed HTML document

maincontent.html. To actually use the template system, simply create an instance of the quick_template class and pass it your array as part of the constructor and call the parse_template method which will return the parsed document:

<?php

$temp_vals = array('main'=> array('file' => 'mytemp.thtml'),

'title' => array('content' => 'Welcome to my web page!'),

'date' => array('content'=> date('M jS, Y")),

'bodyattribs' => array('content'=> 'BGCOLOR=#FFFFFF'),

'content' => array('file' => 'maincontent.thtml'));

$mytemp = new quick_template($temp_val);

echo $mytemp->parse_template();

?>

Since we haven't discussed it yet, before I show you the output of my quick_template script, let's assume the file maincontent.html contains the following:

Hello, welcome to my webpage!

Now that you know what all the pieces are, the final output of this script will be:

<HTML>

<HEAD>

<TITLE>Welcome to my web page! - Dec 7th, 2002</TITLE>

</HEAD>

<BODY BGCOLOR=#FFFFFF>

Hello, Welcome to my webpage!

</BODY>

</HTML>


Publication Date: Wednesday 23rd July, 2003
Author: John Coggeshall View profile

Related Articles