Templating with PHP
Writing easily maintainable code is a challenge, and you must plan ahead to make the process as painless as it can be. With a web-based language like PHP, one of the primary requirements for clear, lucid code is separation of logic and layout. This is an important thing, but what is the best way to achieve this? Unlike JSP or ASP.NET, which both promote separation of layout and logic by the themselves (eg. ASP.NET's codebehind feature, both technologies' special tags), or Ruby, Perl and Python, which are usually used with a web framework, PHP implies the opposite: by letting developers jump out of scripting into HTML and back in again with a simple "<?php" and "?>", PHP is the best language for creating a code soup by mixing HTML and PHP into an entropic, unmaintainable mess of a script.Since few choose to use a whole PHP framework (and assuming you don't either), the only way you're going to achieve this effect is by willingly imposing on yourself certain rules of how to construct PHP pages. Fear not, there is a beautiful little method web developers use to split their code and layout, and that is: templates.
A templating system functions in the following way: you store some smaller chunks of HTML somewhere, with special placeholders within them that will get replaced by the content of your choice. Then, in your script, you just put the template code into a variable and replace it with the content you've generated. These template placeholders can also be replaced by whole other templates, so you can effectively break down your page into smaller pieces that you put together in the way you choose. This is quite possibly one of the most efficient and handy ways to control the output of your pages.
So like I've said, a template is a piece of HTML code containing some "dynamic stuff". What this is depends almost entirely on what the templating system can handle - it can be just variables that act as placeholders for data such as usernames, links, ID numbers, etc, or executable code (or pseudo-code) that you can use to control how the template is rendered from within the template itself. The rest of this article will assume you just want to insert variables into a template, since embedding executable code is a bit of a quick hack, and I've never had the need to do it.
A script using a templating system is usually a lot shorter and it looks like all you're doing is checking permissions and setting variables, and then at the end you call some procedures and a page is outputted. Having your site use a templating system will save you lots of time, and allow you to focus more on getting the site logic right (you have as much space as you like for doing it properly, there is no HTML crowding you and making your code difficult to read) and less time on thinking how your site will look like. There are other fantastic benefits. For example, if you decide to redesign your site, you can leave the code as it is, and just edit the templates. You can rearrange the template variables, remove some if you deem them unnecessary, and since no HTML remains directly coded into the website, the new web interface can have nothing in common with the previous, but it will still do the same things and contain the same information.
Or, for example, if more people are working on a web site, and some specialise in the design of a site, while some are coding it (splitting into two teams, design and coding is a common choice), a templating system is the best solution, since the web designers can make the web interface without knowing any PHP whatsoever (all they have to know is the names of the variables that should be embedded into the content, and what each will display) and the developers can write the server-side coding without having to pay any attention to making the output look nice. And if they are stored in a database, the templates can be loaded with exceptional ease and efficiency. With a decent templating system, a template needs only to be extracted and loaded once: afterwards it can be used a limitless number of times, and it can contain different data each time. Suffice to say, templates rock, and I'll leave the discovery of how templates make your application better in other ways to you. Now let's take a pleasant tour around the workings of a good templating system.
A templating system is simpler than it looks. The whole thing should start with a "template declaring" of sorts. This usually happens somewhere near the start of a script; but essentially before you "cache" the templates. What this really is is declaring an array of the names of the templates you're going to be using (every template having a unique name that you'll refer to it by) in your script, so they can all be retrieved by a caching process (this means you can fetch them all in one query if you choose to store them in a database, or one file open otherwise). I have the code for caching templates in a globally included script that loads all required classes and functions, so templates are loaded automatically for each page I make. The forum software MyBB does this by declaring a string containing a list of names separated by commas. Vanilla uses a similar thing, but in a pretty stange way. vBulletin makes it's template declaration as an array of strings, each the name of a template. I have found that using an array is the clearest, most practical way. Here's how this would look in PHP.
<?php
$templates = array(
'gretting',
'navigation',
'userpanel',
);
?>
All the array does is state what templates may be used later in the script. A good templating system would allow the loading of templates not stated in the template pre-caching array, but would log this so that the developer could add the template into the declaration. And obviously, the less templates used, the better - so loading all your templates and using only several would be inefficient and unnecessary. This allows you to load what only that what you will use. The purpose of declaring them at the start of the script is so that all the names of the templates can be glued together and converted into a single SQL query: a query for each template wouldn't be as efficient. Here's how this might look:
<?php
if (!isset($templates))
{
// disable all template functionality
define('ELECTRON_DISABLE_TEMPLATES', true);
$templates = array();
}
else
{
$templates = array_merge($templates, array(
'DEFAULT',
'doctype',
'sitestats_box',
'userbox_guest',
'userbox_member',
));
}
cache_templates($templates);
?>
This snippet is real code from an early version of this website. The first part checks if the templates were pre-cached. The second part adds some always-used templates to the array to be cached. This is useful if you have templates that you use on each page, and you don't want to write them out every time. In the first if statement, if the templates weren't pre-cached at all (no $templates was declared), the script assumes you don't want templates active at all (useful for some scripts like redirect pages, where you want to avoid the overhead of loading templates) and defines a constant that is checked for in cache_templates(). If it exists, the function just returns. But if it isn't, it takes the templates and puts the array pieces together into a string, and queries for templates. It then stores them in some variable to be used later in the script.
You may notice that one template has been written with all capital letter. This is just a convention I have in place: an uppercase template is one that prints a whole page out, from <html> to </html>, and lowercase templates are just HTML fragments, like the doctype declaration, a table of information or other. What other templates do I have uppercase? Well, for example, I chose to use templates for generating XML feeds too - one is called RSS, one ATOM and similar. They both output the main structure for their respective formats.
Ok, so now you reach the point where you have stated what templates you want to use, and they have been extracted from the database and are in a variable, a logical name for it might be $templatecache. This variable is an associative array of information with template names as keys and template HTML as values. It's likely to be very big (still, depending on the number of loaded templates) so make sure you don't "lose it" somewhere or create it twice. Memory leaks aren't possible in PHP, but you will still want to only make it once, naturally. You may want to fiddle around with the efficiency of your templating system, by echoing out the size of the $templatecache variable (using sizeof()). Then try and split up your HTML into more templates and see if that reduces it's size. If it does, it means that you have reused HTML, definitely a good thing. And it's important to know that as easy as it would be to simply copy and paste a whole page into the database and call it a template, it would be inefficient (lots of the same HTML) and that's not what a template system is about: it's about small pieces of HTML being put together. If you had basic calculus, you'll know that integrals only become accurate when dx approaches zero - I admit that's not entirely the case here, because it's easier on the developer (that means you) to have small templates, not one- or half-tag templates! This whole site has only a few uppercase, full-page templates, so if you want it efficient, take your time creating your templates. They will save you lots of work and are as efficient as it gets if implemented correctly.
A template cache is a big variable, and you just used a relatively large amount of time and memory to make it, so it's no good to you sitting idle! Let's see how you can put your templates to good use. The templates are currently just HTML with PHP variables in them. The PHP variables haven't been replaces with their values yet, so after you cache templates it's time to use your super coding skills to do whatever the script does. For instance, some globally-used script might check cookies and find out if the currently registered user is logged in, and then set a variable to contain a bunch of links for users or for guests (login, register and home for guests) and one for logged in users (logout usercp and home). This variable will take some place in your template, so you have to set it before you evaluate the variables in the template - have them replaced with their values. Your main template (DEFAULT, for example) might conatin the positions of other templates:
{$templatevars['doctype']}
<html>
<head>
<title>{$templatevars['title']}</title>
</head>
<body>
{$templatevars['bodycontent']}
</body>
</html>