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.