I cringe every time a client starts a call with: “So, we decided to write our own in-house CMS.” Building a CMS is easy; building one correctly is hard.
First of all, if WordPress will do what you’re looking for, the don’t re-invent the wheel. It’s a decent piece of software, it’s well-supported, and as long as you avoid third-party plugins and pay attention to updates, it’s reasonably secure.
However, often what the client is really looking for is just a simple master template inclusion system. Often what they end up with is a mess of good intentions, RewriteRules, and somewhere something that looks like this:
<? include($BASEDIR . "/" . $_GET['page'] . ".php");?>
Without going into how terrible an idea this is, allow me to introduce an alternative. It’s simple, it’s intuitive, and it’s easier to work with than whatever mess you narrowly avoided making.
We start out with the pages themselves. They look pretty simple. The important bits are the require
bit at the top, any page-specific variables you’d like to set, and then all the HTML content you want to have in the page.
<? require_once("/var/www/lib/template.php")?>
<?
$TITLE = "My Page";
$DESC = "A simple page about nothing";
?>
<h1>Hello world</h1>
<p>Pages are easy!</p>
Next let’s write up that template.php
file. It’s where the magic happens. I’d recommend putting this file outside your document root. Same goes for your template files and any other files that shouldn’t be browsed directly.
<?
function __template_shutdown_function_1() {
global $TEMPLATE;
if (empty($TEMPLATE)) $TEMPLATE="main.tpl.php";
$CONTENT = ob_get_clean();
$TEMPLATE_DIR=realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR;
require($TEMPLATE_DIR.$TEMPLATE);
}
register_shutdown_function('__template_shutdown_function_1');
ob_start();
See what we did there? That was clever. We call ob_start()
and register a shutdown function that calls ob_get_clean()
and then includes our master template. So when the template gets included, the output of the page file will end up in the $CONTENT
variable.
We’re calling the main templage main.tpl.php
and sticking it in the same folder as template.php
, but that’s totally up to you. Also note that we allow the page to specify an alternate template file by setting a variable called $TEMPLATE
. Cool. Note that we’re relying on $TEMPLATE
to be empty if not explicitly set in your code, so if you have register_globals turned on (and you shouldn’t), this can seriously bite you.
Finally, note the lack of any closing ?>
tag. This should be reasonably obvious, but if your file doesn’t contain any output, then don’t include the closing tag so you don’t accidentally output something.
OK, so let’s look at this template file.
<?
if (empty($TITLE)) $TITLE="My Site";
if (empty($DESC)) $DESC="";
?><!doctype html>
<html>
<head>
<title><?= $TITLE ?></title>
<meta name="description" content="<?= $DESC ?>"/>
</head>
<body style="background:black;color:green;">
<?= $CONTENT ?>
</body>
</html>
“Wait,” you say, “Is it really that easy?”
Yup. That’s all there is to it. Just plunk that one single include at the top of your pages, and all the styling will be handled by your template file. Most importantly, there’s no need to split up the header and footer, or include the content indirectly from the template, any of those shenanigans.
If you want to specify multiple text blocks in your page file, it’s pretty simple since ob_start()
can be nested. Like so:
<? require_once("/var/www/lib/template.php") ?>
<? $TITLE="Page Two"; ?>
<h1>Second Page</h1>
<p>Now with a sidebar!</p>
<? ob_start(); ################# BEGIN SIDEBAR ### ?>
<ul>
<li>First</li>
<li>Second</li>
<li>Etc</li>
</ul>
<? $SIDEBAR = ob_get_clean(); ##### END SIDEBAR ### ?>
See there? Now you have this nifty $SIDEBAR
variable with content you can insert elsewhere in your template. And best of all, you didn’t have to compose it as a PHP string.
If all you’re looking for is a simple way to keep a consistent style on all of your pages, then it doesn’t get any easier than this. The more code you have, the greater your chances of a security exploit. So do yourself a favor and use the simplest solution available to you.