Quakenet/#php Tutorial

Note: If you opened this page from an external URL pay attention that all chapters are linked together. Be sure you also read all prior chapters of this tutorial, otherwise you will miss relevant content explained before.

Template system

  1. Layout of a template system
  2. Template systems in php
  3. Write your own template system
  4. Pros and cons of the template system

1. Layout of a template system

With a template system you try to split the php script into two parts, one for the logic and one for the output. This is similar to a Model View Controller. The content for the output is excluded to external files. These files are just called templates. If you have split up the parts you can easily change the output of the script by just changing the template file. The file for fetching the data for the templates are the same. This way a webauthor can create template files with his html and css knowledge and don't need to know how to read them out of a mysql database.

2. Template systems in php

There are a lot of template systems for php. Each have advantages and disadvantages, have features others don't have, have security aspects other don't haves and use syntax others don't have. But you forget php itself is already a template engine. PHP is developed to be used inline of html code. For that reason there exists the start and end tags <?php and ?>. So we use php as our template system by using include statements to load template files.

3. Write your own template system

We can load other php files with include as already known. So we use this to load templates and use php code inside these templates and access variables. This also mean we cannot support user templates. It would be to risky to load a user template which contains functions like shell_exec or mail. The admins and webauthors of a homepage should be the only one who can change the template files.

Our template system is controlled by a start script index.php. As always we increase the error reporting level and load some files with functions, classes and variables (depending on the project).

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'functions.php';
include 
'classes.php';
include 
'variables.php';
?>

We use the GET variable section for an array include. We save all valid filenames in an array called $files which can be load with an include statement. The index is used for the value of $_GET['section'].

<?php
// added in the variables.php file
$files = array();
$files['news'] = 'news.php';
$files['forum'] = 'forum.php';
$files['downloads'] = 'dl.php';
// ...
?>

We check with isset and file_exists if the GET variable exists, the index exists and the include file exists which we load with an include.

<?php
$ret 
1// default return value of include
if (isset($_GET['section'], $files[$_GET['section']])) {
    if (
file_exists('includes/'.$files[$_GET['section']])) {
        
$ret = include 'includes/'.$files[$_GET['section']]; 
    } else {
        
$ret "Couldn't load include file: 'includes/".$files[$_GET['section']]."'";
    }
} else {
    
// load default area, like "news"
    
$ret = include 'includes/'.$files['news'];
}
?>

First we see a variable called $ret. We use it to save the return value of our include statement. Then we have some if clauses to check the array indexes and if the include file exists.

For our include files (these are not the template files) we define how the return value should look like for later proceed. In the common case if all wents fine the return value is an associative array. The entry with the index filename got the template filename to load. The entry with the index data is an array and got the values for the template. This is the place to store the news entries so the template can show them (just the data, no html code). If we got some error in our file the return value is a string with the error message to show. And at last the return value can be the integer value 1. This is accidently used if there was no return statement in the include file. We write all these information in a php comment so we don't forget them.

<?php
$ret 
1// default return value of include
/*
 * The include file to load must have a return statement with the
 * following value:
 * - for normal execution:
 *   Array('filename' => string, -- name of the template file
 *         'data' => Array())    -- an array with data for the template
 * - for error execution:
 *   string  -- The error message to show
 */
if (isset($_GET['section'], $files[$_GET['section']])) {
    
// ...
?>

We presume we have such include files working and now need to load the right template file. We do this by checking the value of $ret.

<?php
if (is_array($ret) and isset($ret['filename'], $ret['data']) and
        
is_string($ret['filename']) and
        
is_array($ret['data'])) {
    
// valid return value
    
if (file_exists($file 'templates/'.$ret['filename'])) {
        
$data $ret['data']; // we save the data array in a variable called $data
                              // so the template file can use it
        
include $file;
    } else {
        
// template file doesn't exists, show an error message
        
$data = array();
        
$data['msg'] = 'Template file "'.$file.'" not found.';
        include 
'templates/error.tpl';
    }
} else if (
is_string($ret)) {
    
// include file returns a string, which is an error message.
    
$data = array();
    
$data['msg'] = $ret;
    include 
'templates/error.tpl';
} else if (
=== $ret) {
    
// return statement was missing
    
$data = array();
    
$data['msg'] = 'The include file didn\'t use the return statement.';
    include 
'templates/error.tpl';
} else {
    
// some really weird return value
    
$data = array();
    
$data['msg'] = 'The include file returns an invalid value.';
    include 
'templates/error.tpl';
}
?>

Depending on the return value the corresponding template file is loaded/included. In normal execution the template file in the array is loaded. For any error execution the template templates/error.tpl is loaded. This is the first template we create as it is shown if we have errors.

<?php
/* data:
 *  'msg' - the error message
 */
?><p class="error">
    <?php echo htmlspecialchars($data['msg']); ?>
</p>

The file contains a php comment to explain which data are send to the template file (via the $data variable). In this case its just the field msg with the error message to shown.

Now we write our first example include file. As the news script is the default content to show we begin writing this file. However this chapter doesn't deal how to write a news script so we return some dummy values.

<?php
$a 
= array();
$a['filename'] = 'news.tpl';
$a['data'] = array();

// fetch somehow the news out of the database
if (false) {
    
// if there was an error, just simulate them with if (false)
    
return "There was an error in the database: ...";
}

$news = array();

// fill the array with news entries (from a database)
// we create a fake entry
$newsentry = array();
$newsentry['title'] = 'New homepage';
$newsentry['added'] = '2008-01-01 00:00:00';
$newsentry['content'] = 'Just in time for new year we start our new homepage.';
$news[] = $newsentry;

$a['data']['news'] = $news;

return 
$a// do not forget or $ret gets the value int(1)
?>

You see its depend on some circumstances if a string or array is returned. This is the whole idea of these different return values. If we got an error we just can write return 'error message';. In normal execution we return the array. As this include file wants to load a template file news.tpl we create such a file

<?php
/*
 * data:
 * 'news' -- an array of news entries which the following layout
 *           Array(
 *               'title',  -- The title of the news
 *               'added',  -- Creation time
 *               'content' -- The content of the news
 *           )
 */ 
?>
<?php 
if (count($data['news'])) { ?>
    <?php foreach ($data['news'] as $entry) { ?>
    <h2><?php echo htmlspecialchars($entry['title']); ?></h2>
    <p class="entry">
        <span>Written at <?php echo htmlspecialchars($entry['added']); ?></span>:
        <?php echo htmlspecialchars($entry['content']); ?>
    </p>
    <?php ?>
<?php 
} else { ?>
<p class="info">
    There are no news.
</p>
<?php ?>

First we have a informal comment explaining how the $data array looks like. Then the template itself begins. As you see we switch between php and html mode as we need them. The php part just controls what to show and how. A webauthor doesn't really know php so much to understand how to change the template files.

Beside the content to shown by the include file the whole html structur like header and footer is missing. We load them with include or readfile statements. The complete index.php looks like this.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$ret 1// default return value of include
/*
 * The include file to load must have a return statement with the
 * following value:
 * - for normal execution:
 *   Array('filename' => string, -- name of the template file
 *         'data' => Array())    -- an array with data for the template
 * - for error execution:
 *   string  -- The error message to show
 */
if (isset($_GET['section'], $files[$_GET['section']])) {
    if (
file_exists('includes/'.$files[$_GET['section']])) {
        
$ret = include 'includes/'.$files[$_GET['section']]; 
    } else {
        
$ret "Couldn't load include file: 'includes/".$files[$_GET['section']]."'";
    }
} else {
    
// load default area, like "news"
    
$ret = include 'includes/'.$files['news'];
}

// load of the html header
include 'templates/html_header.tpl';   // Doctype, <html>, <head>, <meta> stuff
include 'templates/html_body_tag.tpl'// or just a single echo "<body>";
include 'templates/menu.tpl';          // if you have a menu

// load of template file
if (is_array($ret) and isset($ret['filename'], $ret['data']) and
        
is_string($ret['filename']) and
        
is_array($ret['data'])) {
    
// valid return value
    
if (file_exists($file 'templates/'.$ret['filename'])) {
        
$data $ret['data']; // we save the data array in a variable called $data
                              // so the template file can use it
        
include $file;
    } else {
        
// template file doesn't exists, show an error message
        
$data = array();
        
$data['msg'] = 'Template file "'.$file.'" not found.';
        include 
'templates/error.tpl';
    }
} else if (
is_string($ret)) {
    
// include file returns a string, which is an error message.
    
$data = array();
    
$data['msg'] = $ret;
    include 
'templates/error.tpl';
} else if (
=== $ret) {
    
// return statement was missing
    
$data = array();
    
$data['msg'] = 'The include file didn\'t use the return statement.';
    include 
'templates/error.tpl';
} else {
    
// some really weird return value
    
$data = array();
    
$data['msg'] = 'The include file returns an invalid value.';
    include 
'templates/error.tpl';
}

// load html footer
include 'templates/html_footer.tpl';  // stuff like </body> and </html>
?>

If you create new content areas use these steps:

  1. You create a template file. Put a php comment at the top so you know which data should be send to the template file.

  2. You create an include file. This returns the array for the filename and the data array or a string with an error message.

  3. Add the new content area to the $files array so it can be loaded.

4. Pros and cons of the template system

As written above we cannot use this template system for unchecked user templates. It's to dangerous they use functions like mail. As an advantage the output of the script is delayed. This sounds like a disadvantage but this allowes us to change the header with functions like session_start, header and setcookie.

Questions about the chapter

No questions

Back to Next to
Copyright © to the OPs of #php/QuakeNet Valid XHTML 1.0 Strict Valid CSS!