6. Writing the script

We start with writing index.php file which will initialize both libraries and load the action requested in the URL.

<?php
// Initialize the include path and the timezone.
set_include_path(get_include_path().PATH_SEPARATOR.'./models'.
    PATH_SEPARATOR.'./models/generated'.PATH_SEPARATOR.'./libs');
date_default_timezone_set('Europe/London');
try
{
    // Initialize Doctrine autoloader
    require('./libs/Doctrine.php');
    spl_autoload_register(array('Doctrine', 'autoload'));
 
    // Initialize OPL autoloader
    require('./libs/Opl/Base.php');
    Opl_Loader::setDirectory('./libs/');
    Opl_Loader::register();

The first thing that needs to be configured is include_path, where we must add the paths to the model directories. Then we start the try...catch block to capture the exception from the libraries and initialize the autoloaders. Here we will stop for a while to explain some issues concerning them. Open Power Template is actually the first member of a bigger project, Open Power Libs. In order not to duplicate the same functionality, the OPL libraries share a common core located in /libs/Opl/ directory. It provides the autoloader, error handling, configuration and the plugin system. The autoloader itself does not use include_path, but rather provides its own methods to specify the library locations in more flexible way. We decided to use Opl_Loader::setDirectory() that specifies that all the supported libraries are located in the same directory. Then we register the autoloader in the PHP stack.

You should have probably noticed that OPT and Doctrine share the same class naming style. OPL autoloader is a general-purpose tool that is able to handle also other libraries. Theoretically, we could use it to handle Doctrine, too, however we would have to write an extra handler to deal with models. Actually, such handler is included in the second library, Open Power Classes, but because the article is not all about OPT, we can also simply initialize the original Doctrine autoloader before the OPL one.

Now, as the autoloader issue has been explained, we will do some configuration:

    // Enable the extended error messages for debugging purposes
    Opl_Registry::setState('opl_extended_errors', true);
 
    // Load the configuration
    require('./config.php');

While moving to a production server, you should disable opl_extended_errors option. Otherwise, every exception thrown by OPT will be accompanied with a lot of extra information that are useful while debugging, but may be potentially dangerous to the web application and the server. Doctrine can be initialized in the same way, as in the command-line interface script:

    // Initialize Doctrine
    $manager = Doctrine_Manager::getInstance();
    $manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE);
    Doctrine::loadModels('./models/');
 
    $conn = Doctrine_Manager::connection($config['database']['dsn']);
    $conn->setCharset($config['database']['charset']);
    foreach($config['database']['attributes'] as $attributeName => $attributeValue)
    {
        $conn->setAttribute($attributeName, $attributeValue);
    }
    $conn->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
    $conn->setAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER, true);

Next, we must initialize Open Power Template to make it work. Create the Opt_Class object and load the configuration from the array in config.php:

    // Initialize OPT
    $tpl = new Opt_Class;
    $tpl->loadConfig($config['opt']);   
    // Load some extra stuff
    require('./libs/stuff.php');
 
    $tpl->setup();

Moreover, the /libs/stuff.php file adds some extra functions and OPT components that will help us building nice HTML forms with templates. We will back to them later. Once the library is configured, we need to call Opt_Class::setup() method.

Open Power Template is designed similarly to the view layers in popular frameworks. Contrary to most of the available template engines, we do not have a class-for-everything here. OPT features smaller, specialized classes that better reflect the reality. The most fundamental concept are views, the objects of Opt_View class. You may look at them as at the data with accompanying template. One script may create many views in one request and compose the output document from them. You do not have to configure each of them separately (the configuration is kept globally in Opt_Class object) and furthermore, you do not have to worry about the naming collisions between the data in two views. To process a view and display its result, we need another concept, an output system. Output systems are objects of the classes that implement Opt_Output_Interface. OPT provides two standard output systems. One of them is Opt_Output_Http that sends the view processing result to the browser and allows to manage the HTTP headers. Our script will receive the view from the action and display it using the output system:

    $action = 'list';
    if(isset($_GET['action']) && ctype_alpha($_GET['action']))
    {
        $action = $_GET['action'];
    }
    require('./actions/'.$action.'.php');
    $view = action();
    $output = new Opt_Output_Http;
    $output->setContentType(Opt_Output_Http::XHTML, 'utf-8');
    $output->render($view);

As we can see, the library provides also a convenient wrapper to send the Content-type header. In our case, we want to send the information on the XHTML document encoded with UTF-8. Note that OPT actually sends this header only if the browser supports it. In other case, it gets an ordinary text/html header.

Finally, we must also process the exceptions that could be generated during the development process. OPL provides a nice, standard error handling interface that equips the exceptions with lots of useful information that help finding the reason of the problem. Unfortunately, Doctrine does not do so and we have to wrap its exception into a standard OPL exception to get a nice message, too (but without the extended help).

}
catch(Opt_Exception $e)
{
    $handler = new Opt_ErrorHandler;
    $handler->display($e);
}
catch(Doctrine_Exception $e)
{
    $wrapper = new Opl_Exception($e->getMessage());
    $handler = new Opl_ErrorHandler;
    $handler->display($wrapper);
}

At last, we have to add the OPT configuration to config.php file:

$config = array(
    'database' => array(
        'dsn' => 'mysql://user:password@host/dbname',
        'charset' => 'utf8',
        'attributes' => array(
            'use_native_enum' => true
        )
    ),
    'opt' => array(
        'sourceDir' => './templates/',
        'compileDir' => './templates_c/',
        'stripWhitespaces' => false,
        'charset' => 'utf-8'
    )
);

The configuration options for OPT:

  1. sourceDir – specifies the directory for the source templates.
  2. compileDir – specifies the directory for the precompiled templates. PHP must have a write access to it.
  3. stripWhitespaces – as we know, the templates are XML documents, so it is not a problem for OPT to remove the unnecessary white spaces during the compilation and obfuscate the output code. However, in the development process, it is good to disable it.
  4. charset – the encoding for Opt_Output_Http. Actually, we do not need it here, because we have specified it manually in the setContentType() method, but we can also use the configuration to provide such piece of information.

The next thing we have to do is to create the base template with the general structure of the HTML code. We will create the first template for OPT and save it under /templates/layout.tpl. Below, you can find its content:

<?xml version="1.0" ?>
<opt:root include="snippets.tpl">
<opt:prolog version="1.0" />
<opt:dtd template="xhtml10transitional" />
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>{$title} – photo gallery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body>
<div id="header">
    <h1>Photo gallery</h1>
    <h2>{$title}</h2>
</div>
<div id="content">
    <opt:insert snippet="content">
        <p>No content defined</p>
    </opt:insert>
</div>
<div id="footer">
    <p>A sample photo gallery for the article <em>A photo gallery with Doctrine and Open Power Template 2</em> by Tomasz Jędrzejewski.</p>
    <p>Sources available under the terms of X11 license</p>
</div>
</body>
</html>
</opt:root>

OPT is de facto an XML parser and in the default mode we have to keep maximum compatibility with XML standards. Each template must contain an XML prolog which is not displayed in the browser, but used for OPT internal purposes. We need also the main tag and this role is played here by opt:root which allows also to perform some extra operations. In our case, we load the external template, snippets.tpl which will be introduced soon.

Generally speaking, OPT template language consists of two elements:

  1. Instructions – the XML tags and attributes, usually in opt namespace. They control the template processing and perform various manipulations. A single instruction may consist of several tags and attributes.

  2. Expressions, for example $a+$b – they play the same role, as in PHP and the rules used to construct them are very similar. Some syntax elements are modified in order not to cause problems with XML syntax. The expressions can be used as instruction/other tag attribute values or placed directly in the static text, using curly brackets. Their result will be displayed in that place.

The two first instructions that display something are opt:prolog and opt:dtd. They are helper instructions that simplify the prolog and DTD generation for the output document. The second one allows to specify the document type more easily, so that you do not have to remember the whole quite long Doctype address. Later, we display the page title from the variable $title. In the place where we want to display the content of the action, we used opt:insert.

OPT, like any other template engine, supports building the output document from many smaller templates. Otherwise, we would have to rewrite the header and the footer to every single template. However, contrary to Smarty and similar solutions, OPT requires us to follow the XML rules. We are not allowed to begin the <HTML> or <BODY> tag in one template and close it in another one. Instead, we must look at the templates as containers that may include smaller templates within themselves. One of the modularization techniques supported by OPT is template inheritance that came to PHP template engines from Python libraries. The name suggests that it is quite similar to the inheritance in the object-oriented programming. This is a right suggestion and we are going to see why.

The role of the class is played by a single template, and methods are "represented" by snippets. They are pieces of XML code that have been given a unique name. When one template extends another, it may overwrite the snippets defined by the extended template and add some new ones. Similarly to the overloaded methods, overloaded snippets may still refer to their parent "implementations". The only significant difference between OOP and templates are the base templates. They do not contain snippets, but an ordinary content with the free placeholders that run particular snippets. Our layout.tpl is such a base template for the rest of the view. Through the opt:insert instruction we specify, where the action-specific piece of code must be shown, in our case within the <div id="content"> tag. If the requested snippet is not defined, we may also specify the default content.