10.4 The Template Class

Since the application needs to produce multiple output formats, you need a way to control the display. For example, the HTML output starts with <html>, but you certainly don't want that to appear on the command line.

Good programming style says that it's bad form to mix programming and display logic. This leads to messy code because everything becomes intertwined. Additionally, since you already know you need a minimum of two different types of output, doing everything inline not only makes it harder to add additional output styles, but it's also more difficult to maintain your existing styles.

Therefore, you should create template objects. Each object should have the same set of display methods, such as getHeader( ) and getFooter( ). However, they'll return different content?for example, HTML or plain text.

This is the perfect place to use an abstract base class. The abstract class specifies the exact names and prototypes for all your methods. Then, you create one class for each format and make sure that each of those classes extends the base.

The Template class in Example 10-15 has four methods.

Example 10-15. Template abstract class
abstract class Template {

    abstract public function getHeader( );

    abstract public function getBody(DOMDocument $dom);

    abstract public function getFooter( );   

    public function printAll(addressBook $ab) {

        print $this->getHeader( );

        print $this->getBody($ab->toDOM( ));

        print $this->getFooter( );



Two methods?getHeader( ) and getFooter( )?take no parameters. They display the page header and footer, respectively.

In between them is getBody( ). It requires a DOMDocument, which should contain an XML address book full of the information that you want to print.

The last and only nonabstract method, printAll( ), is a convenience method that calls the other three methods and prints them out.

10.4.1 Creating an HTML Template

The HTML class is mostly static HTML, but the getBody( ) method is generated dynamically, as shown in Example 10-16.

Example 10-16. htmlTemplate class
class htmlTemplate extends Template {

    public function getHeader( ) {

        $action = $_SERVER['PHP_SELF'];

        $header = <<< _HEADER_





        <title>Address Book</title>



<form action="$action" method="POST">

<select type="select" name="mode">

    <option value="add">Add</option>

    <option value="search" selected="selected">Search for</option>


<label for="firstname">First Name</label>

<input type="text" name="firstname" id="firstname" size="8"> 

<label for="lastname">Last Name</label>

<input type="text" name="lastname"  id="lastname" size="14">

<label for="email">Email</label>

<input type="text" name="email" id="email" size="14"> 

<input type="submit" value="Do it!">



        return $header;


    public function getBody(DOMDocument $dom) {

        $body = "<table>\n";

        $people = simplexml_import_dom($dom);

        foreach ($people as $person) {

            $body .= "<tr>\n<td>" . $person->firstname . ' ' . 

                      $person->lastname . "</td>\n<td>" . 

                      $person->email . "</td></tr>\n";


        $body .= "</table>\n";

        return $body;    


    public function getFooter( ) {

        $footer = "</body>\n</html>\n";

        return $footer;



While getHeader( ) is mostly static HTML, it contains an HTML form that allows you to both insert new people into and search the address book. The form's action is set to $_SERVER['PHP_SELF'], so your script also processes the form. The input elements are named the same as the properties of your Person class. This allows you to instantiate a Person object from the form without needing to munge the input data.

The getBody( ) method in Example 10-16 takes a DOM object, but that doesn't mean it needs to parse the XML with DOM. SimpleXML is the perfect way to handle this basic type of XML document.

The simplexml_import_dom( ) function converts an object from DOM to SimpleXML. Now it's no problem to create an HTML table: you iterate though the SimpleXML object using foreach, turning every $person into another HTML table row. See Section 5.4 for more on this extension.

10.4.2 Creating a Plain-Text Template

The text template, shown in Example 10-17, is less complex than the HTML version because its header and footer are empty.

Example 10-17. textTemplate class
class textTemplate extends Template {

    private $nameLength;

    private $emailLength;

    public function _ _construct($nameLength = 30, $emailLength = 30) {

        $this->nameLength  = (int) $nameLength;

        $this->emailLength = (int) $emailLength;


    public function getHeader( ) { 



    public function getBody(DOMDocument $dom) {

        // 7 is the size of the column divider 

        // spacers: "| ", " | ", and " |"

        $lineLength = $this->nameLength + $this->emailLength + 7;

        $text = str_repeat('-', $lineLength) . "\n";

        $people = simplexml_import_dom($dom);

        foreach ($people as $person) {

            $name = $person->firstname . ' ' . $person->lastname;

            $text .= sprintf("| %{$this->nameLength}s 

                             "| %-{$this->emailLength}s |\n", 

                              $name, $person->email);


        $text .= str_repeat('-', $lineLength) . "\n";

        return $text;    


    public function getFooter( ) { 




This class has a constructor with two optional arguments, $nameLength and $emailLength. These parameters control the length of the printed name and email columns, respectively. They both default to 30, but you can adjust them if your data is particularly long or short.

Again, the getBody( ) method is the most interesting of the three methods. Since there's no way to automatically place data into a table from the command line, the method uses str_repeat( ) and sprintf( ) to recreate a table. The calls to str_repeat( ) create the top and bottom table borders.

Inside the foreach, sprintf( ) formats the individual lines. The string that reads | %{$this->nameLength}s | %-{$this->emailLength}s |\n has two special codes. The first, %{$this->nameLength}s, turns the input into a $this->nameLength character-wide, right-aligned, space-padded string. The second, %-{$this->emailLength}s, does something similar, but aligns the data on the left side instead of the right.

10.4.3 Displaying Multiple Output Formats with a Template

Now that you've defined your templates, the next step is printing out address books. This is done in Example 10-18.

Example 10-18. Printing addressBook results using templates
try {

    // Create address book and find all people

    $ab = new addressBook;

    $ab->search(new Person);

    // Create HTML template

    $template = new htmlTemplate( );


    // Convert address book XML and print results


} catch (Exception $e) {

    // Error


Figure 10-1 shows the rendered output.

Figure 10-1. HTML version of the address book

Alternatively, setting $template to a new textTemplate produces:


|                 Rasmus Lerdorf | rasmus@php.net                 |

|                   Zeev Suraski | zeev@php.net                   |