4. Programmer's Guide
4.6. Data formats
4.5. Working with sections
« Previous
4.6.1. Available data formats
Next »

4.6. Data formats

Table of Contents

OPT introduces an unique feature called "data formats". Finally, you do not have to worry about the data structures while implementing a template. The idea is simple. In the template syntax we have a thing called generic containers: $variable, $container.item etc. We know, what they are supposed to do, but it is not said, what they are - arrays, objects or maybe something more. Before you start the template compilation, your script may choose the exact format for them. In other words, the script can tell that the container $foo is accessible like an object, and the code $foo.bar will be compiled as $this->_data['foo']->bar. The other container, $goo, may become an array, and then we will get $this->_data['goo']['hoo'] for $goo.hoo. This gives us some benefits. Let's consider some real world scenarios.

  1. We have some people responsible for the templates in our team. They wish to start writing templates for the article module, but the project designer and the programmers have not worked on this module yet and some of the solutions are a big unknown. With other template engines and PHP itself, the template designers may risk and write those files, implementing some default structures, but if the programmers decide to change something, the templates must be corrected as well. Such problem does not occur in OPT. The template designers may use generic containers and let the programmers to choose the exact format in the script. They can even create the templates while the project designers are still working on the code structure project.

  2. The template designers finished their job and completed the templates for the article module. However, the programmers work on another part of the website, and the customer wants to inspect the module layout and navigation. One of the programmers may write a demo version that fills the template with sample data put into the simple arrays. Once the module is written, the arrays may be replaced with the database calls, but still - the template source files remain the same, they just need to be recompiled.

  3. The programmers are doing the code refactoring and they have noticed an inconsequence in the data generated by the script. In some places, it makes a database call, retrieves the data, puts it to the template engine, but part of the templates does not use them. The decide to retrieve the data on demand, so they write a class that implements the Opt_Generator_Interface and bind it to the template containers. The templates themselves still remain untouched. All they have to do is to recompile them, and OPT will do the rest.

Using data formats

In order to demonstrate, how to use formats, we need a simple template:

<opt:if test="$user.read">
    <p class="userinfo">{$user.name}, you have already read this article</p>
    <p class="userinfo">{$user.name}, you have not read this article yet. <a parse:href="'rate.php?uid='~$user.id">Rate it!</a></p>

This template has two generic containers: $article and $user. At this stage, we don't know, what they really are and in the script we can choose the exact format, depending on our needs:

class Article
    public $title;
    public $body;
    public function __construct($title, $body)
        $this->title = $title;
        $this->body = $body;
    } // end __construct(); 
} // end Article;
$view = new Opt_View('article.tpl');
$view->article = new Article('Some article', 'Lorem ipsum dolor sit amet...');
$view->user = array(
    'id' => 53,
    'name' => 'Johnny',
    'read' => true, // he has already read this article
// Now, we choose the format
$view->setFormat('article', 'Objective');

If we do not specify any special format for a variable or container, the compiler assumes that they are PHP arrays. However, in our case the $article is neither an array nor it implements the ArrayAccess interface. Using the Opt_View::setFormat() we inform the compiler that $article is an object by choosing the Objective format for it.

More complex containers

OPT also provides the support for more complicated container calls, like $foo.bar.joe. $foo is an object, while $bar is an object that implements our own Awesome_Access_Interface (let's assume that we already have an OPT format implementation for it). To mark this fact, we may do the following thing:

// Access the 'foo' items as object calls
$view->setFormat('foo', 'Objective');
// Access the 'foo.bar' items with our awesome access interface
$view->setFormat('foo.bar', 'AwesomeAccess');


It can be said that sections are built on OPT data formats. We start from a sample template:

<p>Our customers:</p>
    <opt:section name="customers">
    <li>{$customers.firstName} {$customers.lastName}</li>

In case of sections, we can find two areas where the data formats may work:

  1. Section itself - how to iterate over the elements and how to access them. The format affects the <opt:section> and </opt:section> tags.
  2. Section elements - how to access the element variables, like $customers.firstName and $customers.lastName.

OPT provides the control over both of the areas. If the section iterates over an object that implements the Iterator interface, but the elements are arrays:

class Customers_Rowset implements IteratorAggregate
    private $_customers = array(0 =>
        array('firstName' => 'John', 'lastName' => 'Sawyer'),
        array('firstName' => 'Adam', 'lastName' => 'Brown'),
        array('firstName' => 'Jennifer', 'lastName' => 'Wells')
    public function getIterator()
        return ArrayIterator($this->_customers);
    } // end getIterator();
} // end Customers_Rowset;
$view->customers = new Customers_Rowset;

OPT formats implement the decorator pattern and this feature is used in sections. We specify the format for the section and decorate it with the section element format:

$view->setFormat('customers', 'Objective/Generic');

If both sections and their elements are of the same format, we can simply write Objective instead of Objective/Objective.

Nested sections are supported by specifying the format for each of them separately:

<opt:section name="categories">
<div class="category">
    <opt:section name="products">

The PHP code:

$view->setFormat('categories', 'Objective/SingleArray');
$view->setFormat('products', 'Objective');

The top section data are always taken from the variable buffer. Nested section data are treaded as one of the parent section elements, so the data for products will be retrieved using SingleArray format.

Note that sections do not put any assumptions on the element indexes, data structure type or element order resolving. It is a data format issue and depends on the chosen one. For example, for Array and SingleArray the section elements require element indexes to start with 0 and grow continuously. On the other hand, Objective accepts any kind of element indexes. The details can be found in the data format descriptions. Your templates should never assume that a particular index type is available. For any special uses, consider writing your own data format, which is descriped later.

Hiding the fact that a variable or section is actually global

Normally, we use the $global container to access the global variables. However, sometimes we would like to hide this fact, especially when it comes to sections. Since OPT 2.0.1, we can use Opt_View::setFormatGlobal() here:

// Sets a format for '$global.foo' in all the templates
Opt_View::setFormatGlobal('foo', 'Format');
// Sets a format for '$foo' in all the templates
Opt_View::setFormatGlobal('foo', 'Format', false);

Supported feature issue

Not all of the formats can be used everywhere. Each of them implements a particular set of features. For example, RuntimeGenerator works only with sections and cannot be used for section elements or generic containers.

4.6. Data formats
4. Programmer's Guide
« Previous
4.5. Working with sections
Next »
4.6.1. Available data formats