Invenzzia » Resources / Articles / A photo gallery tutorial / The first action

7. The first action

It is the right time to create our first action that displays the list of uploaded image thumbnails. We will begin with the template to continue the inheritance issue started in the previous chapter. The following content must be saved in /templates/index.tpl file:

<?xml version="1.0"?>
<opt:extend file="layout.tpl">
    <opt:snippet name="content">
        <opt:show name="items" cols="5">
            <table class="photos">
            <opt:grid>
                <tr>
                    <opt:item><td><a parse:href="'index.php?action=preview&id='~$items.id"><img parse:src="'photos/thumb/'~$items.filename" parse:alt="$items.title" /></a></td></opt:item>
                    <opt:emptyItem><td></td></opt:emptyItem>
                </tr>
            </opt:grid>
            </table>
        <opt:showelse><p>No photos</p></opt:showelse>
        </opt:show>
 
        <p class="links"><a href="index.php?action=photo">Upload a photo</a></p>
    </opt:snippet>
</opt:extend>

This template extends layout.tpl, using opt:extend instruction instead of opt:root. Within this tag, we are allowed to use opt:snippet instructions only – the other content will be ignored by the compiler. Because the base template needs a snippet named content, we create it and specify the content to be shown. Our main page is going to display a list of image thumbnails in five columns. It is a quite annoying task that leads to very ugly code full of mathematical operations that check whether the end of the row is reached etc. But why should we waste our time to implement it, if OPT provides us a ready and tested solution?

OPT introduces the concept of sections to display different types of lists. You can think of them like of smart loops. There are several types of sections, depending on the list type we want to get. However, all of them are designed using the same rules and can cooperate one with another. Contrary to PHP, we do not have to worry about how such loop works and how is implemented. All we have to do is to specify the look of a single list element and give the section a name, so that OPT could find the data for it. Here, we use the opt:grid section that is designed especially to display the items in columns. The exact number of columns can be controlled with cols attribute. Within the tag, we have to use two more tags: opt:item that specifies a single list element and opt:emptyItem – the look of an empty item for the last row, if the number of list elements is not a multiplicity of cols. Moreover, we do not want to display the table, if there are no uploaded images, so we enclose the whole section within opt:show. Opt:showelse lets us define the alternative text that should be displayed, if there are no elements in the list.

Let's get back to the look of a single thumbnail, because it illustrates, how to create a dynamic value of HTML tag attribute. We must not use curly brackets then. Instead, we change the namespace of the attribute to parse to indicate that it contains a value that must be processed by OPT. Please note, how we refer to the section element variable: $sectionName.variableName. There are no iterators, array accessors, object accessors etc. - the whole template is independent from PHP data types. The expression 'index.php?action=preview&id='~$items.id is a simple string concatenation. OPT uses the ~ operator for concatenation (an idea taken from D programming language), because dot is reserved for other purposes.

To sum up, we see that with OPT, the exact behavior and even data types in the template are not a subject of our concern anymore. Does the template mention that the section data must be arrays? No, because it does not have to. The compiler selects the data type during the compilation using the information from the script. Similarly, we do not need to reinvent the wheel and write the whole algorithm to display the photos in the columns.

After this quite long introduction to the templates, we get back to the PHP code and Doctrine where we will be introduced into DQL. Save the source code of the main page to /actions/list.php:

<?php
function action()
{
    $view = new Opt_View('index.tpl');
    $view->title = 'Photo list';
    $view->items = Doctrine_Query::create()
        ->select('id, title, filename')
        ->from('Photo')
        ->orderBy('id DESC')
        ->execute(array(), Doctrine::HYDRATE_ARRAY);
    return $view;
} // end action();

The code required by the action controller is reduced to the absolute minimum. All we have here is a simple function called action(). The action must fill the OPT view with some data retrieved from the database and return it to be displayed by index.php. The most interesting code snippet is undoubtedly the Doctrine part which sends a query to the database. DQL is very similar to SQL and you should have no problems with switching to it. The main difference is the fact that we are operating on models rather than database tables. DQL can be represented in two levels: the plain text query and objective representation. For the sake of performance, the first one should not be used very often, as Doctrine needs to parse such query and translate it to SQL. The Doctrine_Query::create() method creates the query object and various additional methods such as select() or orderBy() let us add different clauses to it.

Someone may say that this is only a visual difference, but this is not true. Contrary to SQL, Doctrine does know the relationships between the models and automatically applies the joins coming out of the foreign keys. For example, a query using two models would look like this:

Doctrine_Query::create()
    ->select('a.*, b.*')
    ->from('Table1 a')
    ->innerJoin('a.Relation b');

If Doctrine knows that the two models are connected with a relationship, the expression a.Relation b is parsed correctly and does not result in producing a Cartesian product. The differences are even more visible, if we take a look at the result structure. SQL gives us the flat data, whereas Doctrine converts them into a hierarchical tree according to the relationships.

The last action that must be performed on a query object is the call of execute() method. The first argument represents the additional query parameters (usually we provide an empty array here) and the second one – the result hydration mode. By default, Doctrine hydrates the data into objects but due to the performance reasons we should use arrays unless we need the extra model functionality. The library returns the result similar to the one below:

array(0 =>
    array('id' => 1, 'title' => 'Photo 1', 'filename' => 'file.jpg'),
    array('id' => 2, 'title' => 'Photo 2', 'filename' => 'file.jpg'),
    array('id' => 3, 'title' => 'Photo 3', 'filename' => 'file.jpg'),
    // etc.
);

It is also a correct default data format for OPT sections.