I don't know about you, but all these documents about dividing web programming into logic, presentation, and content always irritate me. Most of them miss an important point: often at least three people develop a web page--the programmer (i.e, the PHP or Perl guru), the web designer who provides the presentation (the HTML designer), and the content developer (perhaps a marketing person). If you're working for a multilingual company, you probably have to make the pages available in several different languages as well, with one marketing person responsible for each language. Most documentation on this subject tends to forget, or at least doesn't bother to notice, either the web designer or the marketing person.
I bring up this subject because, as the programmer, I'm usually the one who has to come up with a solution. The marketing people simply don't understand the problem. While the web designer probably understands, he or she may not know how to solve it. When I point out this issue at a project meeting, everybody looks at me with the "you're the genius here, so come up with something brilliant!" look. Genius or not, they're right.
This article will demonstrate how to divide logic from presentation and
presentation from content. In the end, a web page will consist of three files:
one PHP file with all page logic, one HTML file containing the presentation,
and one or more language files that will hold the texts for the page. The
language file to use for a specific request will be determined on-the-fly by
examining the current site. (www.mysite.com/index.php will
present information in English, www.mysite.se/index.php in
Swedish, and www.mysite.de/index.php in German.)
To separate logic, presentation, and text into different files, we need a template engine. Most PHP projects today use Smarty for this, but we won't. Smarty puts too much logic into the template itself. To avoid confusing or upsetting the web designer, the core template must look as much like a regular HTML page as possible. That means no strange looking iteration code and such. Instead, we'll use FastTemplate. FastTemplate may not be as advanced and featureful as Smarty, but its syntax is definitely better suited for this problem.
Let's look at a concrete example of what differentiates FastTemplate and Smarty. Suppose we want to iterate through a set of database records, presenting a list of products to sell online. With Smarty, this would be a very simple task; we would just add something like the code below to the template HTML file:
{section name=products loop=$ProductName}
{$smarty.section.products.iteration}
{$ProductName[products]}
{$ProductPrice[products]}<br />
{/section}
What's the big deal about this? It looks quite smooth. However, when the web designer opens the HTML document containing this code in Frontpage or Dreamweaver, what do you think the reaction is? "What the heck is this?! Remove, remove, remove..." However, if we put this logic into the PHP file instead, like FastTemplate does, the web designer never has to see this logic, and the chance of getting a valid template file back increases. With FastTemplate, you register the iteration in the PHP file, but only mark it with a standard HTML comment in the template:
<!--
BEGIN DYNAMIC BLOCK: products -->
<tr>
<td>{ProductName}</td>
<td>{ProductPrice}</td>
</tr>
<!--
END DYNAMIC BLOCK: products -->
FastTemplate uses the two comments to identify the dynamic block, and then parses it according to the logic in the PHP file. This setup is cleaner, as it doesn't mix up the logic with the presentation. Also, the web designer won't need to bother about the logic at all but can just open then template in any program, make the necessary changes, and then upload it to the server (as long as the comments stay intact, that is). Let's see how this would work in reality.
To make it easier to create many web pages using this technique, we'll start by defining a framework to include into other PHP files. This framework consists of two files.
header.php will be included at the top of every PHP
file. It makes sure FastTemplate is initiated correctly. Then, by
using a couple of variables in the $_SERVER array, it
includes the correct template file and language file, and initiates
FastTemplate to use these files. footer.php tells FastTemplate to parse the template
and language file into a human-readable result. It then prints this
result to the browser.To use this framework, each PHP file will include header.php at
the top and footer.php at the end. Any logic for the web page goes
in between.
header.php is:
1: <?
2: include_once ( "./FastTemplate/class.FastTemplate.php4" );
3: $php_self_us = eregi_replace ( "\.", "_", $_SERVER['PHP_SELF'] );
4: $tld = strrchr ( $_SERVER['SERVER_NAME'], "." );
5: $tld = substr ( $tld, 1 );
6: include_once ( $_SERVER['DOCUMENT_ROOT'].$php_self_us."_" .$tld.".lang");
7: $tpl = new FastTemplate ( $_SERVER['DOCUMENT_ROOT'] );
8: $tpl->assign( $text );
9: $tpl->define( array( page => $php_self_us.".html" ) );
10: ?>
On line 2, we include the FastTemplate class, making it
available for use. Line 3 puts the name of this PHP file, with all dots
replaced by an underscore, in the variable $php_self_us. So, if
the name of your PHP script that has included header.php is
index.php, $php_self_us will contain
index_php. This variable is used on line 6 and line 9 to include
the correct language and template files.
In lines 4 and 5 we parse out the TLD (top level domain) for the current
server. Line 4 copies the last dot and everything after it from
$_SERVER['SERVER_NAME'] to the variable $tld. If your
server name is http://www.myserver.com/, the value of
$tld will be .com. Line 5 then uses
substr() to remove the leading dot of $tld.
Line 6 includes the correct language file for this request. If your document
root is /usr/local/apache/htdocs, the name of the PHP script is
index.php; and if the server name is
http://www.myserver.com/, then the complete path to and name of
the file included here will be
/usr/local/apache/htdocs/index_php_com.lang. This file should
define all text for the .com version (probably in English) of your
site in an array called $text. We'll examine that more closely a
little later.
Next, on line 7, we create a new FastTemplate object, using the document
root as the location of all template files. Line 8 assigns the texts defned in
the language to the FastTemplate object. In practice, this means FastTemplate
will be able to replace all text constants in the template file with the real
text. On line 9, we tell FastTemplate which template file to use. If the PHP
were named index.php, this call would search for a template file
called index_php.html.
footer.php is very simple. It ensures that the FastTemplate
instance was created correctly, parses the template defined in the last line of
header.php, then prints the result to the browser:
1: <?
2: if( isset($tpl))
3: {
4: $tpl->parse( PAGE, page );
5: $tpl-> FastPrint( PAGE);
6: }
7: ?>
Note that we don't call print() or echo()
manually. The FastTemplate->FastPrint() function takes care of it.
|
We now have a good framework to work with, so let's see how to implement a web page with this technique. To start, we'll need to write a template that defines the layout of the page, language files that hold the texts for our two fake web sites, and a PHP file that incorporates the template, the texts, and some additional logic into an actual web page. Four files will be created:
index_php.html (the template)index_php_com.lang (the texts in English)index_php_se.lang (the texts in Swedish)index.php (the logic)Remember that the language file included will be based on the name of the
host on which the PHP script is currently executing. For this example, I have
set up two fake servers: http://www.myfakedomain.com/ and
http://www.myfakedomain.se/. To use the same files, they must
both use the same document root. These are the virtual host directives that
were added to Apache's httpd.conf for these two hosts:
<VirtualHost *>
DocumentRoot /usr/local/apache/htdocs/myfakedomain
ServerName www.myfakedomain.com
</VirtualHost>
<VirtualHost *>
DocumentRoot /usr/local/apache/htdocs/myfakedomain
ServerName www.myfakedomain.se
</VirtualHost>
We now have to place the four files listed above in /usr/local/apache/htdocs/myfakedomain.
Let's start with the template. It's the same for both sites, although you
could use the TLD technique on that one very easily as well. The common way,
however, is to use the same layout, and provide it in several different
languages. index_php.html is listed below, just as it looked when
we got it back from the designer at MyFakeDomain):
<html>
<head>
<title>{TITLE}</title>
</head>
<body bgcolor='lightblue'>
<center>
<font color='blue'>
<font face='arial' size='4'><b>{HEADING}</b></font>
<font face='arial'><p>{TEXT}</p>lt;/a>
<table border='1' bgcolor='darkblue'>
<tr>
<th>
<font color='yellow'>{NAME}</a>
</th>
<th>
<font color='yellow'>{POSITION}</a>
</th>
</tr>
<!-- BEGIN DYNAMIC BLOCK: employees -->
<tr>
<td>
<font color='yellow'>{C_FIRSTNAME} {C_LASTNAME}</font>
</td>
<td>
<font color='yellow'>{C_POSITION}</font>
</td>
</tr>
<!-- END DYNAMIC BLOCK: employees -->
</table>
<font face='arial'><p>{LOCAL_TIME}: <b>{C_LOCAL_TIME}</b></p></font>
</font>
</center>
</body>
</html>
This is an ordinary HTML document with two important
differences. As you see, there's no actual text in it, only
FastTemplate constants. These constants, which always must be wrapped
inside curly brackets, will be replaced by actual text when the
template gets parsed. Also, there is a dynamic block, called
employees, defined in this template. The PHP logic will
identify this block and repeat it as many times as it finds required,
each time generating a table with a dynamic number of rows in
it. There's nothing special about that, you might be thinking, but
here we are doing it from a plain HTML template, without any ugly
logic in there at all.
To see which FastTemplate constants are really constant (that is, defined in
one of the language files), and which ones are generated at execution-time by
the PHP logic, I like to add the prefix C_ to the generated
constants. In my world, C stands for "current", but you can of
course use whatever you want. Just make sure you agree with yourself on a
standard.
The language files, one received from the English-speaking marketing guy and
the other received from the Swedish-speaking, define the constant texts in the
template. The English version, index_php_com.lang is listed
below:
<?
$text = array(
'TITLE' => "Welcome to MyFakeDomain",
'HEADING' => "MyFakeDomain",
'TEXT' => "Below is a listing of all employees at MyFakeDomain:",
'NAME' => "Name",
'POSITION' => "Position",
'LOCAL_TIME' => "Local time"
);
?>
The text snippets are defined in an array called $text. If you
remember, we assigned the contents of this array to FastTemplate in
header.php at line 8. Since the name of this array is hardcoded
into header.php, it must go by the name $text in all language
files.
The Swedish version of the language file, index_php_se.lang
looks almost the same, but this time, the actual texts are written in
Swedish:
<?
$text = array(
'TITLE' => "Vlkommen till MyFakeDomain",
'HEADING' => "MyFakeDomain",
'TEXT' => "Nedan finns en lista ver de anstllda p MyFakeDomain",
'NAME' => "Namn",
'POSITION' => "Position",
'LOCAL_TIME' => "Lokal tid"
);
?>
One of these language files will be included and assigned to FastTemplate in
header.php. That will determine in which language to present the
information. For this example, we have only two language files since we only
have two web sites. However, you could have ten, twenty, or a thousand
different language files and sites, and this technique would work equally
well.
Finally, we'll also need some PHP logic to tie the texts and the template
together into a complete web page. This PHP code also needs to generate the
dynamic constants we referred to in the template (the ones starting with a
C_), and also parse through the dynamic block
(employees). The script that does all this is, of course, called
index.php:
1: <?
2: include_once ("header.php");
3:
4: $people[] = array(
'firstname' => "John",
'lastname' => "Doe",
'position' => 'Developer');
5: $people[] = array(
'firstname' => "Sven",
'lastname' => "Svensson",
'position' => 'Web Designer');
6: $people[] = array(
'firstname' => "Jose",
'lastname' => "Perez",
'position' => 'Head of Marketing');
7:
8: $tpl->define_ dynamic(" employees","page");
9: foreach ($people as $person)
10: {
11: $tpl->assign( array(
12: 'C_FIRSTNAME' => $person['firstname'],
13: 'C_LASTNAME' => $person['lastname'],
14: 'C_POSITION' => $person['position']
15: ));
16: $tpl->parse( ROWS,".employees");
17: }
18:
19: $tpl->assign( array('C_LOCAL_TIME' => date("H:i:s")) );
20:
21: include_once ("footer.php");
22: ?>
We always need to include header.php at the top of
every PHP file, so we do that directly at line 2. Now remember that
when line 2 is finished, FastTemplate has been set up, and the correct
template file and language file has been included and assigned to
FastTemplate.
On line 4, 5 and 6, we create an example array, called $people,
of employees at our fictive company. This array is created so that we have some
sample data to use with our dynamic block in the template
(employees). For a real application, this could have been replaced
by a call to a database or a request to a web service.
Line 8 tells FastTemplate about the dynamic row. We do this by calling
FastTemplate->define_dynamic(), passing the name of the dynamic
block and the name of the template in which the dynamic block appears as
parameters. Note that the name of the template should not be the name of the
actual file, but the name we assigned to it when registering it to FastTemplate
(line 9 in header.php).
Lines 9 through 17 iterate through $people. Each row of the
array is assigned to the constants C_FIRSTNAME,
C_LASTNAME, and C_POSITION (lines 11 through 15), and
is then parsed into the dynamic block (line 16). Also note how the call to
FastTempate->parse() differs a little from the call we make in
footer.php; here we use a leading . (dot) on the
template name (which in this case is a dynamic block) to tell FastTemplate that
we want to append information to the value ROWS, and not truncate it. Then,
when FastTemplate->FastPrint() is called in
footer.php, the dynamic block in the template will be replaced by
the value of ROWS.
On Line 19 we define the constant C_LOCAL_TIME, which, if you
remember, was referred to in index_php.html. As you see, we use
date() to generate a string representing the current time (in the
format HH:MM:SS), storing that string in
C_LOCAL_TIME. Since the value of this constant is calculated upon
each request, every page will show the current time.
As outlined by the framework, we need to include footer.php at
the end of each PHP file. We do that at line 21.
Everything seems to be in place now. We have the template, the
texts, and the logic. Let's test it! On my local network, I have set
up the fake zones myfakedomain.com and
myfakedomain.se on the name server, and I've pointed
www.myfakedomain.com and www.myfakedomain.se
to the web server. When I direct my browser at
http://www.myfakedomain.com, I see a page similar to
Figure 1.

Figure 1. Content in English
If I make a request to http://www.makefakedomain.se, I'll see
something more like Figure 2.

Figure 2. Content in Swedish
Both sites have the same layout and the same logic, but with different texts. This is the result of three independent modules tied together!
After making this technique the standard at the company I work for, both web designers and people involved in text development have a good understanding how this work. By setting up and configuring FTP clients on their machines, they can check out existing templates or language files, make changes to them, and upload them back to the server. Dynamic web pages on our site are updated in no time, without the involvement of any technical staff. If text needs to be removed, a marketing person simply sets that variable to empty in his or her language file. If a particular text snippet should be removed from all versions of a web page, the marketing people just asks a web designer to remove the reference to that particular constant in the template.
Sometimes the marketing people decide they want some additional dynamic
content added to a web page. For example, once one of the web designers called
me and said "Hey Daniel! You know, X at marketing called and said she and Y had
decided they want to present our current price of Z on the front page. I added
a reference to c-underscore-z-underscore-price in the template, so do you think
you could get me that variable from the database?" I just added some additional
logic to the PHP file for our front page that connected to our database,
retrieved the current price of Z, and stored the price in FastTemplate as
C_Z_PRICE (of course, with Z replaced by the real name of that
product). The marketing people got what they wanted in half an hour or so,
everyone doing only their part of the work.
If needed, this concept also allows moving some of the design into the language files when needed. For example, it might turn out useful to use a constant from a language file to set the background colour for a page or table, or the font for some text. By this, marketing people can control some of the layout without the involvement of a web designer. This can be good if the web designer is not available, and the marketing people have a scheduled change for a particular page. However, after I discover this kind of abuse, I usually try to look at them with a little annoyed grin on my face when I meet them in the corridor. Everyone should do what they are supposed to; that's the whole idea.
Finally, you should keep in mind that the original FastTemplate distribution for PHP doesnt work very well with PHP4. I used the PHP4-compatible version of FastTemplate for this example. If you use PHP3, or are looking for the official documentation, take a look at http://www.thewebmasters.net/.
I hope you will find the techniques presented in this article useful.
Daniel Solin is a freelance writer and Linux consultant whose specialty is GUI programming. His first book, SAMS Teach Yourself Qt Programming in 24 hours, was published in May, 2000.
Return to the PHP DevCenter.
Copyright © 2007 O'Reilly Media, Inc.