Web application structure with OPT

  • Published on 23 April 2010 08:06:52 GMT by Tomasz Jędrzejewski
  • pl

This tutorial explains different template modularization techniques available in Open Power Template and shows how to use them to create a flexible template rendering environment for a typical web application.

Table of Contents

Open Power Template, as well as any other template engine, is always deployed within a web application together with other libraries. The key to make it successful is the proper structure of our project. The topic of this tutorial covers creating a structure of a web application that uses Open Power Template and template modularization techniques offered by the library. We assume that the reader already knows object-oriented programming and has a basic knowledge about frameworks.

1. How does OPT work?

Let's take a look at a sample source code for the popular Smarty 2 template engine:

$smarty = new Smarty;
$smarty->template_dir = './templates/';
$smarty->compile_dir = './templates_c/';
$smarty->caching = true;
 
$smarty->assign('foo', 'bar');
$smarty->display('template1.tpl');
 
if(!$smarty->is_cached('template2.tpl'))
{
    $smarty->assign('bar', 'joe');
}
$smarty->display('template2.tpl');

In the example above, we have a single object of a universal class for everything. Smarty deals with the configuration, collecting the script data, template processing, rendering, caching and many more. It is rather easy to use the library, especially for the beginning programmers who have little experience with object-oriented programming. However, it is not suitable for bigger, objective applications, because it breaks many OOP rules.

Contrary to Smarty, Open Power Template does not have a single, central class. The tasks are divided among several smaller units. Below, we show a simplified UML diagram describing the main part of the library:

UML diagram for OPT

Simplified UML diagram for OPT

There are several smaller classes connected with different relationships:

  • Opt_Class - main class responsible for managing the configuration.
  • Opt_View - these objects represent a template with the data associated to it.
  • Opt_Output_Interface - an output system interface. Output systems are responsible for launching the template execution and sending the result somewhere (i.e. to the browser).
  • Opt_Caching_Interface - caching system interface.

One of the available output systems is Opt_Output_Http which sends the generated content to the browser. In addition, it provides the HTTP header management. The next one is Opt_Output_Return which returns the content as a result of the render() method. The programmer can easily create a new output system that suits the custom needs, i.e. generating the e-mail body. Another extensions, such as implementing a new caching system or adding new methods to Opt_View are simple, too. All we have to do is to extend an existing class or implement an interface.

The practical example of the API usage can be seen below. Firstly, our script must create an object of Opt_Class and save the requested configuration in it. Next, we create one or more views and bind them to templates and the script data. Finally, we put everything to one of the output systems which produces the complete result:

// Configure the library
$opt = new Opt_Class;
$opt->sourceDir = './templates/';
$opt->compileDir = './templates_/c';
$opt->setup();
 
// Create views
$moduleView = new Opt_View('modul.tpl');
$moduleView->foo = 'bar';
 
$layoutView = new Opt_View('layout.tpl');
$layoutView->module = $moduleView;
$layoutView->title = 'Tytuł';
 
// Render everything
$output = new Opt_Output_Http;
$output->render($layoutView);

In most frameworks, the Opt_View class would represent the entire view. However, this is not fully correct approach according to the MVC pattern. Opt_View represents just a template with the data, whereas the view layer should also be responsible for retrieving the data from a model and implementing the view logic (i.e. pagination).

2. Modularization techniques

A single template never contains the complete output code. Instead, the code is generated from several smaller templates providing certain parts of the output, such as the general structure, menu or content. We will call it modularization, because usually each application module has its own, custom template. We do not specify what the module actually is. It could be a controller action, a plugin or any other logical piece of code you need that displays something.

The simplest modularization technique is to take the scissors and cut the code in several places, producing files such as header.tpl, footer.tpl etc. Next, we parse them in the requested order. However, Open Power Template actively defends itself against scissors and does not allow you to do so. There are two reasons for that. Firstly, OPT templates are XML documents, so you cannot open <body> tag in one file and close it in another. Another problem is the flexibility. The menu location is hard-coded in the application and if we had a layout where menu code should appear somewhere else, we would have to redesign the application structure.

As a result, we see that templates must be consistent. We are not allowed to simply cut them in several smaller files, so the library must provide an alternative. Indeed, OPT provides you two basic but powerful modularization techniques: including and template inheritance. We are going to describe them now.

Including templates

To include a template within another, we use opt:include instruction. As an argument, it takes either a view object or the template name. OPT looks for the template in the repository and executes it in the specified place. The effect resembles the PHP include() command. Of course, everything happens dynamically, so we can control what to show at any time. To illustrate the idea, let's take a look at the example:

$layoutView = new Opt_View('layout.tpl');
$layoutView->layoutData = 'Layout data';
 
if($action == 'foo')
{
    $view = new Opt_View('foo.tpl');
    $view->data = 'Some data';
}
else
{
    $view = new Opt_View('bar.tpl');
    $view->data = 'Some other data';
}
$layoutView->module = $view;
 
$output->render($layoutView);

The layout.tpl file looks like this:

<?xml version="1.0" ?>
<opt:root>
 
<p>This is my website.</p>
 
<!-- include a view stored in the $module variable -->
<opt:include view="$module" />
 
<p>Footer</p>
</opt:root>

Depending on the chosen action, the script displays different views - either with foo.tpl or bar.tpl template. We pass it to the template engine and to opt:include which loads it and displays in the requested place. Note that the included view contains custom variables and it does not see the layout view data. We can see it for ourselves by writing the following template foo.tpl:

<?xml version="1.0" ?>
<opt:root>
<p>My data: {$data}</p>
<p>Layout data: {$layoutData}</p>
</opt:root>

The result would be:

My data: Some data
Layout data:

The first variable is displayed correctly, as it belongs to the current view. However, $layoutData is a part of view layout which is not accessible here. In PHP functions, the situation is similar. Each function has its own variable scope and sees neither caller nor global variables... unless we import them. OPT lets you passing arguments to the view which is useful in some cases:

<opt:include file="template.tpl" />

We include a template, but specify its name explicitely. OPT creates a new, temporary view for it, but it does not contain any local variables. We can pass them as attributes to opt:include:

<opt:include file="template.tpl" foo="$variable" />

Now, the included template contains the $foo variable with the value of the attribute expression. View arguments work for the view loading (opt:include view), too. Finally, we may request to share the variable scope with the caller:

<opt:include file="template.tpl" import="yes" />

The import attribute imports all the calling view variables to the local view. Note that they are not references, so the caller would not see any changes made by the included template.

The file attribute expects a string by default. If we want to load the template name from a variable, we have to use the following construction: <opt:include parse:file="$variable" />.

The last interesting feature of opt:include is the support for exceptional situations. If we tried to load an unexisting template, OPT would throw an exception by default. However, we may force a different reaction: loading a different template or a default content:

<opt:include view="$view" default="noTemplate.tpl" />
 
<opt:include view="$view">
    <p>Cannot load the specified template.</p>
</opt:include>

Snippets

Snippets are a kind of macros. We may save a commonly used code into a snippet and insert it everywhere we need it. The modification is not a problem anymore. We have to edit the snippet only and the changes will be automatically propagated to all templates. Snippets are processed during compilation and they have two important features:

  1. They match to the context in a given template. For example, if our snippet contains a variable $foo and we insert it within a section foo, it starts to point to the current section element. In other places, $foo may be an ordinary variable. Data formats act similarly.
  2. We cannot select a snippet to execute dynamically, reading the name i.e. from a variable. The library must know everything during compilation and cannot be changed later.

Let's take a look at a sample code. A popular technique is to create a file called snippets.tpl, save there the snippets and load it in every template, so that we have an easy access to them. We will put there a pagination layout:

<?xml version="1.0"?>
<opt:root>
 
 <opt:snippet name="pagination">
    <div id="pagination">
        <div class="info">Page {$pageNumber} of {$pageCount}</div>
        <div class="list">Go to page:
            <opt:selector name="pages">
                <opt:page><a parse:href="$pages.url">{$pages.number}</a></opt:page>
                <opt:active><a parse:href="$pages.url" class="active">{$pages.number}</a></opt:active>
            </opt:selector>
        </div>
    </div> 
 </opt:snippet>
 
</opt:root>

Then we go to a concrete template where we would like to have pagination. All we have to do is to load snippets.tpl and insert the snippet:

<?xml version="1.0"?>
<opt:root include="snippets.tpl">
 
<h1>Product list</h1>
 
<p>Some list here...</p>
 
<opt:insert snippet="pagination" />
 
</opt:root>

Snippet files must not be loaded with opt:include, as it is a run-time instruction, so we make use of include argument in opt:root instruction. Such a file will be loaded during compilation and analyzed against snippets. Any other code in the template is ignored.

It is also possible to replace a content of a tag with a snippet:

<div opt:use="snippet">
    <p>Default content</p>
</div>

If the snippet is not loaded, OPT shows the default content instead of overwriting it.

Starting from Open Power Template 2.1, snippets can take arguments:

<?xml version="1.0"?>
<opt:root>
 
    <opt:snippet name="mySnippet" arg="required">
        <p>An argument: {$arg}</p>
    </opt:snippet>
 
    <opt:use snippet="mySnippet" arg="$variable" />
</opt:root>

For the sake of consistence, opt:insert instruction has been renamed to opt:use. There are a few such modifications in the new version, but we do not have to worry about them thanks to a compatibility mode and a template converter.

Remember that the compiler does not see snippets included with opt:include instruction.

Procedures

Procedures are a completely new feature introduced by Open Power Template 2.1. At first sight, they are very similar to snippets. We enclose a piece of code in them, specify arguments, give the name and use everywhere we need. However, there is one important difference. Procedures work on runtime which has the following consquences:

  1. Data formats and the way the procedure work are defined once and for all during compilation. All the templates the procedure is used must match it.

  2. We can dynamically select the procedure we wish to run.

Below, we see an example where the script decides what procedure is going to be used to display the loop content:

<?xml version="1.0"?>
<opt:root>
    <opt:procedure name="way1" data="required">
        <p>{@data.title}</p>
        {u:@data.body}
    </opt:procedure>
 
    <opt:procedure name="way2" data="required">
        <h1>{@data.title}</h1>
        {u:@data.body}
    </opt:procedure>
 
    <opt:section name="list">
        <opt:use procedure="$list.procedureName" data="$list" />
    </opt:section>
</opt:root>

Contrary to snippets, procedure arguments are registered as local template variables (followed by @ symbol instead of $ to avoid naming collisions). The procedure has also the full access to the variables of the view it was called in.

Unless you need a dynamic selection, we recommend to use snippets. They are processed during compilation which results in a faster execution, and furthermore they cause less problems thanks to the automatic context matching.

Template inheritance

Template inheritance is the most advanced modularization feature available in Open Power Template. It is based on snippets, so it is processed during the compilation (however, there is a trick that makes it more dynamic). The idea was borrowed from object-oriented programming. The templates act as classes, where the methods are replaced with snippets. The snippets can be overwritten by an extending template which may also add new snippets. The only difference is the base template. It does not contain any snippet, but the base HTML code where we put the snippets from extending templates. The describtion may seem very complex and abstract, so let's take a look, how to use it in practice. We start by writing the base template base.tpl:

<?xml version="1.0"?>
<opt:root>
<opt:prolog />
<opt:dtd template="html5" />
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title opt:use="titleSnippet">Default title</title>
 
        <opt:insert snippet="extraHeaders">
        <link rel="stylesheet" type="text/css" href="design/style.css" media="all"  />
        </opt:insert>
    </head>
    <body>
        <div id="header" opt:use="header">
            Default header.
        </div>
 
        <div id="content" opt:use="content">
            Default content.
        </div>
 
        <div id="footer" opt:use="footer">
            <p>© My Company 2010</p>
        </div>
    </body>
</html>
</opt:root>

Using opt:insert and their attribute version opt:use we have defined some placeholders that will be filled in by the extending templates depending on certain modules. If the template does not create a snippet for a given placeholder, OPT uses the default content from the base template. Now we can create a template for the home page (index.tpl):

<?xml version="1.0"?>
<opt:extend file="base.tpl">
    <opt:snippet name="title">My website</opt:snippet>

    <opt:snippet name="extraHeaders">
        <opt:parent />
        <link rel="stylesheet" type="text/css" href="design/news.css" media="all"  />
    </opt:snippet>

    <opt:snippet name="content">
        <h1>Welcome to my web page</h1>

        <p>Welcome to my web page!</p>
    </opt:snippet>
</opt:extend>

In an extending template, opt:root has been replaced with opt:extend. In the file attribute we specify the template to extend. OPT ignores the entire content of opt:extend except snippets which are displayed in the placeholders from the base template. Notice that the original, default content is not definitely lost. It can be still displayed in the overwriting snippet with opt:parent tag. It can also display the content of an overwritten snippet. This feature allows us to use the default CSS files defined in the base template even if we replace it with a custom extraHeaders snippet. Of course, index.tpl can be extended by another template which will add or overwrite some more snippets and so on. We get a chain of templates that produce a complete output during the compilation.

However, template inheritance has some limitations. The templates are executed within a single OPT view and they share the variables. What is more, we cannot select a template we want to extend on runtime because everything is calculated during compilation. The dynamic nature of template inheritance is based on a trick. We cannot load the template name from a variable, but we can create a special channel to pass such information to the compiler. In the template we just inform the compiler that it should expect dynamic data:

<?xml version="1.0"?>
<opt:extend file="base.tpl" dynamic="yes">
...

On the script side we have to use the inherit(extending template, extended template) method:

$view->inherit('index.tpl', 'differentLayout.tpl');

In this way we make index.tpl extend differentLayout.tpl instead of base.tpl as it is specified in a template. The extended template can be changed at any time, but we must remember that every possible combination is compiled separately. If we provide too many choices, OPT will produce a lot of compiled templates for all the possible cases.

There is also another dynamic feature called branches:

<?xml version="1.0"?>
<opt:extend file="base.tpl" simplified="simplified.tpl">
...

In the script:

$view->setBranch('simplified');

This time the script still has an opportunity to choose the case it needs, but lets the template to decide what physical template is extended in a specific case (which is called inheritance branch). If a template does not have the specified branch defined in the opt:extend tag, OPT uses the default file from file attribute which has to be always present.

3. Setting up the application structure

So far, we have met different OPT features that help us modularizing our templates. Ity is the right time to show, how to build something useful with them. We are going to write a template processing environment for our web application, assuming that we utilize a typical web framework with controllers and actions within them. Within a single HTTP request, more than one action can be executed and the actions may decide whether to run another one or not. We would like to define a standard layout and action-specific content. The best choice would be opt:include instruction and OPT views, providing us the variable separation. Each action could be executed independently and we do not have to worry about naming collisions between template variables. The environment will select a default template for each action automatically, using the names of controller and action. The layout manager will manage the main layout and collect the view objects from the actions. Finally, it will generate a complete output.

Let's begin with a directory structure:

/templates
   /controller1
       /action1.tpl
       /action2.tpl
       /action3.tpl
   /controller2
       /action1.tpl
       /action2.tpl
       /action3.tpl
   /snippets.tpl
   /layout.tpl
   /baseLayout.tpl
   /error.tpl

As we see, each controller has its own directory with the action templates. Directly within /templates, we place application layouts, a template with commonly used snippets and an error handling template.

On a single page, there may be displayed more than one view coming from different actions, however - they do not have to be displayed in one certain place. Our template rendering environment should be able to know that i.e. action X wants to be displayed under menu and action Y - in the content panel. To achieve that, we can simply create more placeholders in a layout. Each placeholder is represented by a section that iterates through the list of views and displays them.

<?xml version="1.0"?>
<opt:root>
<opt:prolog />
<opt:dtd template="html5" />
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title>{$title}</title>
    </head>
    <body>
        <div id="header">
            Default header.
        </div>
 
        <div id="menu">
            <!-- first placeholder -->
            <opt:section name="menu">
                <opt:include from="menu" />
            </opt:section>
        </div>
 
        <div id="content">
            <!-- second placeholder -->
            <opt:section name="content">
                <opt:include from="content" />
            </opt:section>
        </div>
 
        <div id="footer">
            <p>© My Company 2010</p>
        </div>
    </body>
</html>
</opt:root>

The construct opt:include from is a typical syntactic sugar equivalent to <opt:include view="$menu.view">. Notice that there is absolutely no problem to define new layouts using a template inheritance, creating multiple variants built over a common core.

In our template, there are two placeholders defined: menu and content. The unit responsible for them is the layout manager which is going to be written now. When it comes to the action templates, they are ordinary templates that we fill as we wish.

That's all on the template side. The rest issues concerns PHP programming. Of course, we will not write here a complete framework, assuming that it is actually ready. It is your task and your exercise to match the presented examples to your situation. As we said, we are going to start with a layout manager which is a class that collects the view objects from actions, assign them to placeholders and render the layout.

<?php
/**
 * Layout manager class.
 *
 * @author Tomasz Jędrzejewski
 */
class LayoutManager
{
    /**
     * Placeholder list with their content.
     * @var array
     */
    private $_places = array();
    /**
     * Layout view
     * @var Opt_View
     */
    private $_layoutView = null;
    /**
     * Layout name
     * @var string
     */
    private $_layout = 'layout';
 
    /**
     * Assigns a specified view to the placeholder.
     *
     * @param string $place The placeholder name.
     * @param Opt_View $view The assigned view.
     */
    public function appendView($place, Opt_View $view)
    {
        if(!isset($this->_places[(string)$place]))
        {
            $this->_places[(string)$place] = array();
        }
        $this->_places[(string)$place][] = array('view' => $view, 'template' => $view->getTemplate());
        $view->appended = true;
    } // end appendView();
 
    /**
     * Sets the layout name.
     *
     * @param string $layoutName The new layout name without the '.tpl' extension.
     */
    public function setLayout($layoutName)
    {
        $this->_layout = (string)$layoutName;
        if($this->_layoutView !== null)
        {
            $this->_layoutView->setTemplate((string)$layoutName.'.tpl');
        }
    } // end setLayout();
 
    /**
     * Returns the name of the used layout.
     *
     * @return string
     */
    public function getLayout()
    {
        return $this->_layout;
    } // end getLayout();
 
    /**
     * Returns the layout view, lazy-loading it if necessary.
     *
     * @return Opt_View
     */
    public function getLayoutView()
    {
        if($this->_layoutView !== null)
        {
            $this->_layoutView = new Opt_View($this->_layout.'.tpl');
        }
        return $this->_layoutView;
    } // end getLayoutView();
 
    /**
     * Renders the layout and views.
     */
    public function render()
    {
        $layout = $this->getLayoutView();
 
        // Prepare placeholders.
        foreach($this->_places as $name => $data)
        {
            $layout->assign($name, $data);
        }
 
        // Render
        $output = new Opt_Output_Http;
        $output->setContentType(Opt_Output_Http::XHTML, 'utf-8');
        $output->render($layout);
    } // end render();
} // end LayoutManager;

Using the setLayout() and getLayout() methods we can select the layout used by our web application. Furthermore, to configure the layout view itself, we get getLayoutView() method returning the OPT view. Assigning the action view to the placeholder is done by appendView() where we have to specify the placeholder name. The manager automatically packs the view to the correct placeholder and informs the other units that it has already been appended (by setting the appended variable). We will see soon why we need it.

Now we must write a simple controller that will simplify our life by automatic view creation.

/**
 * Base controller.
 */
class Controller
{
    /**
     * Action view.
     * @var Opt_View
     */
    public $view;
 
    /**
     * The controller name.
     * @var string
     */
    public $controllerName;
 
    /**
     * Executes actions with the specified name.
     *
     * @throws Exception
     * @param string $name The name of the action to execute.
     */
    public function execute($name)
    {
        if(!method_exists($this, $name.'Action'))
        {
            throw new Exception('The specified action '.$name.' does not exist.');
        }
        // Creating the action view.
        $this->view = new Opt_View($this->controllerName.'/'.$name.'.tpl');
 
        // Let's execute the action.
        $name = $name.'Action';
        $this->$name();
 
        // If the action haven't appended the view anywhere on its own, let's
        // append it to the default placeholder.
        if(!$view->appended)
        {
            $layout = Opl_Registry::get('layout');
            $layout->appendView('content', $this->view);
        }
    } // end execute();
} // end Controller;

The controller should assign the action view to the manager only if the action has not done it on its own. This is where the assigned variable becomes useful for us - thanks to it we know whether the action has done it or not.

Finally, we must set up everything in the bootstrap file:

// Here we create and configure OPT
 
// Creating the layout manager.
$layout = new LayoutManager;
Opl_Registry::register('layout', $layout);
 
// Let's execute everything
$dispatcher->dispatch();
 
// Rendering everything.
$layout->render();

Now we can test our structure, creating a sample controller and an action:

class IndexController
{
    public function indexAction()
    {
        $this->view->variable = 'Foo';
    } // end indexAction();
 
    public function customAction()
    {
        $this->view->someData = 'Some data';
 
        // Appending the view to a different placeholder.
        $layout = Opl_Registry::get('layout');
        $layout->appendView('menu', $this->view);
    } // end customAction();
} // end IndexController;

To sum up, our application is equipped with a class called a layout manager. We specify there the layout template that creates the core of the HTML code for our website. For each executed action, the controller creates automatically an action view, using the controller and action names: controllerName/actionName.tpl. During the execution, the action may assign the generated data to the view. Once the execution is finished, the view is taken by the layout manager to be appended to one of the layout placeholders. Having all actions executed, the layout manager takes the layout view, puts there the action view objects and renders it with the HTTP output system available in OPT and we get a complete HTML code in the browser.

4. Using other features in our environment

Our template processing environment utilizes the opt:include instruction, so we can ask a question, where to use the rest of the modularization features. As we mentioned before, the template inheritance can be used to derive a group of similar layouts from a single core template. It is also very useful for forms, because a single form is often used in many different places, sometimes with additional fields. All the commonly used template parts should be stored as snippets in snippets.tpl and in case of OPT 2.1 - in procedures, too. We do not recommend using components and template functions to represent some commonly used parts, because they are designed for different purposes. Many OPT beginners make this mistake, inspired by other template engines where it was often the only way.

In many applications, there appear some common pages, such as the confirmation whether we want to execute a certain operation. They may be used by many actions. The best choice is to declare them as controller methods that modify the $this->view property, setting it to such a common template and configuring it.

5. Conclusion

The purpose of this article was to show the possible template modularization features available in OPT. We have learned how to use opt:include, snippets, procedures and template inheritance, and then - we set up the complete, flexible template rendering environment. As an exercise, we recommend to extend it with new functionality and do some experiments to get to know the idea of how it works better.

Open Power Template 2.0 projects tutorial

This text is licensed under Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States license.

Questions and feedback

This is the end of this article. Please, leave a comment.