home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Web Database Applications with PHP \& MySQLWeb Database Applications with PHP \& MySQLSearch this book

13.2. Templates

Separating code from HTML can be difficult in PHP. As we discussed in Chapter 1 and have shown throughout this book, one of the best features of PHP is that scripts can be embedded anywhere in HTML documents. However, this can lead to maintenance problems: if we want to redesign the presentation of the web site, then we may need to rewrite code or, at the very least, understand how PHP and HTML are interleaved in the application. This also makes it difficult to maintain code when it is interleaved with presentational components.

A good solution for medium- to large-scale web database applications is to use templates to separate markup and code. In this section, we illustrate how templates can be used in PHP applications through a case study example from the online winestore. In our example, we use the open source XTemplate class library available from http://sourceforge.net/projects/xtpl/. The XTemplate library is object-based, and Chapter 2 provides a brief introduction to the object-oriented features of PHP. There are other excellent template libraries, including most notably the Smarty PHP template engine available from http://www.phpinsider.com/php/code/Smarty/.

13.2.1. Templates in the Shipping Module

Example 13-1 and Example 13-2 show a template module that displays the order receipt. This script, called shipping.3, is a replacement for the shipping.2 script discussed in Chapter 12. The output of retrieving Example 13-2 with a Netscape web browser is shown in Figure 13-1. Example 13-1 is the application logic, and Example 13-2 is the template.

Example 13-1. shipping.3 provides an order receipt

<?php
  include "xtpl.p";
  include "include.inc" ;

  set_error_handler("errorHandler");

  function show_HTML_receipt($custID, $orderID, $connection)
  {
    // Create a new XTemplate object called $xtpl
    $xtpl= new XTemplate ("example.shipping.3.xtpl");

    // Find customer information
    $query = "SELECT *
              FROM customer
              WHERE cust_id = $custID";

    if (!($result = @ mysql_query ($query, $connection)))
       showerror( );

    // There is only one matching row
    $row = @ mysql_fetch_array($result);

    // Assign the orderId to the template
    $xtpl->assign("ORDER_ID", $orderID);

    // Assign the customer data to the template
    $xtpl->assign("CUSTOMER", $row);

    // Parse the template data
    $xtpl->parse("main.customer");

    $orderTotalPrice = 0;

    // list the particulars of each item in the order
    $query = "SELECT  i.qty, w.wine_name, i.price,
                      w.wine_id, w.year, wi.winery_name
              FROM    items i, wine w, winery wi
              WHERE   i.cust_id = $custID
              AND     i.order_id = $orderID
              AND     i.wine_id = w.wine_id
              AND     w.winery_id = wi.winery_id
              ORDER BY item_id";

    if (!($result = @ mysql_query ($query, $connection)))
       showerror( );

    // Add each item to the email
    while ($row = @ mysql_fetch_array($result))
    {
       // Work out the cost of this line item
       $itemsPrice = $row["qty"] * $row["price"];

       $orderTotalPrice += $itemsPrice;

       $wineDetail = showWine($row["wine_id"], $connection);

       // Assign the qty, wine details, price, and 
       // total item cost to the template
       $xtpl->assign("QTY", $row["qty"]);
       $xtpl->assign("WINE", $wineDetail);
       $xtpl->assign("PRICE", 
              sprintf("%-.2f", $row["price"]));
       $xtpl->assign("TOTAL", 
              sprintf("%-.2f", $itemsPrice));

       // Parse a template row of items
       $xtpl->parse("main.items.row");
    }

    // Assign the order total to the template
    $xtpl->assign("ORDER_TOTAL", 
           sprintf("%-.2f", $orderTotalPrice));

    // parse all items
    $xtpl->parse("main.items");

   // parse the whole document
   $xtpl->parse("main");

   // output the templated data
   $xtpl->out("main");
}

  // Main ----------

   // Re-establish the existing session
   session_start( );

   // Check if the user is logged in
   if (!session_is_registered("loginUsername"))
   {
      session_register("message");
      $message = "You must login to view your receipt.";

      // Redirect the browser back to the login page
      header("Location: example.order.1.php");
      exit;
   }

   // Check the correct parameters have been passed
   // unless the script is run correctly
   if (!isset($custID) || !isset($orderID))
   {
      session_register("message");
      $message = "Incorrect parameters to " .
                 "example.shipping.3.php";

      header("Location: $HTTP_REFERER");
      exit;
   }

   // Check this customer matches the custID
   if ($custID != getCustomerID($loginUsername, NULL))
   {
      session_register("message");

      $message = "You can only view your own receipts!";
      header("Location: example.order.1.php");
      exit;
   }

   // Open a connection to the DBMS
   if (!($connection = @ mysql_pconnect($hostName,
                                      $username,
                                      $password)))
      showerror( );

   if (!mysql_select_db($databaseName, $connection))
      showerror( );

   // Show the confirmation HTML page
   show_HTML_receipt($custID, $orderID, $connection);
?>

13.2.1.1. The application logic

Example 13-1 is the application logic that produces the order receipt. The script logic is identical to that of the shipping.2 script discussed in Chapter 12. The different features of this script are the omission of any code to produce output, and the inclusion of the fragments of code that assign database values to presentation elements, parse these elements, and call functions in the template class library.

The script in Example 13-1 works as follows:

  1. Include the xtpl.p template library.

  2. In the function show_HTML_receipt( ), associate the script with the template shown in Example 13-2:

    $xtpl= new XTemplate ("example.shipping.3.xtpl");

    This creates a new template object called $xtpl.

  3. Query the customer table and assign the returned $row to an element of the template named CUSTOMER. Also assign the orderID session variable to an element of the template named ORDER_ID. Uppercase strings are used to distinguish template elements from variables in the script, but this isn't essential. After assigning the data, parse the main.customer template data (we discuss the structure of the template later in this section).

  4. Retrieve and assign each item in the order to the template elements QTY, WINE, PRICE, and TOTAL. Now check that this data is correctly formed and associated by parsing it. The following lines perform these functions:

    // Assign the qty, wine details, price, and total item
    // cost to the template
    $xtpl->assign("QTY", $row["qty"]);
    $xtpl->assign("WINE", $wineDetail);      
    $xtpl->assign("PRICE", sprintf("%-.2f", $row["price"]));     
    $xtpl->assign("TOTAL", sprintf("%-.2f", $itemsPrice));  
    
    // Parse a template row of items
    $xtpl->parse("main.items.row");

    We explain how this relates to the template later.

  5. Assign the overall order total to the template and check the overall structure of the data. The final check includes parsing the items and the overall main output, and then outputting the data with the following code:

    $xtpl->assign("ORDER_TOTAL",
    sprintf("%-.2f", $orderTotalPrice));
    
    // parse all items
    $xtpl->parse("main.items");
    
    // parse the whole document
    $xtpl->parse("main");
    
    // output the templated data
    $xtpl->out("main");

13.2.1.2. The template

Example 13-2 is the template itself. It consists mostly of HTML but also contains a Cascading Style Sheet for presentation. Cascading Style Sheets (CSS) are used to control the styles that present output; references that discuss the CSS standard are listed in Appendix E.

Interleaved throughout the template are the following HTML comments:

<!-- BEGIN: main -->
  <!-- BEGIN: customer -->
  <!-- END: customer -->
  <!-- BEGIN: items -->
    <!-- BEGIN: row -->
    <!-- END: row -->
  <!-- END: items -->
<!-- END: main -->

These comments describe the structural elements referenced in the PHP script in Example 13-1. The elements are nested, so that the element customer is a child of main, because <!-- BEGIN: customer --> occurs inside the <!-- BEGIN: main --> and <!-- END: main --> tags. The customer element can therefore be referenced in Example 13-1 as main.customer. A table row can be referenced in Example 13-1 as main.items.row, because the structural element row is inside items, and items is inside main.

Consider now how the template produces row data:

<!-- BEGIN: row -->
<tr>
  <td>{QTY}</td>
  <td>{WINE}</td>
  <td align="right">$ {PRICE}</td>
  <td align="right">$ {TOTAL}</td>
</tr>
<!-- END: row -->

The tags {QTY}, {WINE}, {PRICE}, and {TOTAL} represent where data is inserted. The data is assigned to these tags in Example 13-1 using the assign( ) function. For example, {QTY} is the position where the order item quantities from the database appear. If there are two database rows assigned to main.items.row elements, two <tr> rows are produced as the body of the HTML <table>.

Example 13-2. HTML XTemplate used by Example 13-1 to output order receipts

<!-- BEGIN: main -->
<!DOCTYPE HTML PUBLIC 
   "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html401/loose.dtd">
<html>
<head>
  <title>Hugh and Dave's Online Wines</title>

  <style type="text/css">
    body {background: #ffffff;
          color: #000000;
          font-family: Arial, sans-serif}
    h1 {font: arial}
    h2 {font: arial;
        font-size: 22}
    a {color: #0000ff;
       font: helvetica;
       font-weight: bold;
       text-decoration: none}
  </style>
</head>
<body bgcolor="white">
<!-- BEGIN: customer -->
<h1>Your order (reference # {CUSTOMER.cust_id} - {ORDER_ID})
has been dispatched</h1>
Thank you {CUSTOMER.title} {CUSTOMER.surname}, 
your order has been completed and dispatched.
Your order reference number is 
{CUSTOMER.cust_id} - {ORDER_ID}. 
Please quote this number in any correspondence.
<br>
<p>If it existed, the order would have been shipped to: 
<br><b>
{CUSTOMER.title} {CUSTOMER.firstname} {CUSTOMER.initial}
{CUSTOMER.surname}
<br>
{CUSTOMER.addressline1}
{CUSTOMER.addressline2}
{CUSTOMER.addressline3}
<br>{CUSTOMER.city} {CUSTOMER.state} {CUSTOMER.zipcode}
<br>{CUSTOMER.country}
</b>
<br>
<br>
<p>We have billed your fictional credit card.
<!-- END: customer -->

<!-- BEGIN: items -->
<table border=0 width=50% cellpadding=0 cellspacing=5>
<tr>
   <td><b>Quantity</b></td>
   <td><b>Wine</b></td>
   <td align=\"right\"><b>Unit Price</b></td>
   <td align=\"right\"><b>Total</b></td>
</tr>
  <!-- BEGIN: row -->
<tr>
   <td>{QTY}</td>
   <td>{WINE}</td>
   <td align="right">$ {PRICE}</td>
   <td align="right">$ {TOTAL}</td>
</tr>
  <!-- END: row -->
<tr></tr>
<tr>
   <td colspan=2 align="left"><i>
     <b>Total of this order</b></td>
   <td></td>
   <td align="right">$<b><i>{ORDER_TOTAL}</b></td>
</tr>
</table>
<!-- END: items -->
<p><i>An email confirmation has been sent to you.
Thank you for shopping at Hugh and Dave's Online Wines.</i>

<form action="example.cart.5.php" method="GET">
<table>
<tr>
   <td><input type="submit" name="home" value="Home"></td>
</tr>   
</table>
</form>
<br><a href="http://validator.w3.org/check/referer">
    <img src="http://www.w3.org/Icons/valid-html401"
     height="31" width="88" align="right" border="0"
     alt="Valid HTML 4.01!"></a>
</body>
</html>
<!-- END: main -->

The customer details are output by accessing the CUSTOMER element. For example, the shipping details are produced with the fragment:

<p>If it existed, the order would have been shipped to: 
<br><b>
{CUSTOMER.title} {CUSTOMER.firstname} {CUSTOMER.initial}
{CUSTOMER.surname}
<br>
{CUSTOMER.addressline1}
{CUSTOMER.addressline2}
{CUSTOMER.addressline3}
<br>{CUSTOMER.city} {CUSTOMER.state} {CUSTOMER.zipcode}
<br>{CUSTOMER.country}
</b>

The overall result of running Example 13-1 and using the template in Example 13-2 is the output in Figure 13-1. The advantage of this approach is that the HTML in Example 13-2 can be altered independently of the script in Example 13-1 and vice versa. This means that application logic and presentation are as separate as possible. The only links between the two are the structural markup components and the embedded tags.

Figure 13-1

Figure 13-1. Output of Examples 13-1 and 13-2

The XTemplate library has more complex features not discussed here. More details on the use of templates can be found at the web sites listed earlier in this section.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.