Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Two Step View

DZone 's Guide to

Practical PHP Patterns: Two Step View

· Web Dev Zone ·
Free Resource
The Two Step View pattern, as its own name suggests, proposes a mechanism composed of two steps for generating an HTML page:
  • first, a logical representation of the page is produced, as a map (key => value) of the different elements of the page.
  • then, the physical representation is rendered, by juggling around the single elements to compose the final HTML.
The first step is usually related to particular page-related presentation code, while the second step introduces some global or category-related presentation, like a common layout.

This pattern kind of composes the previous View ones, because either ones can theoretically be employed in the two steps. As a result, we have several incarnations of this pattern:
  • first step realized with a Transform View, second as a Template View which inserts common elements like navigational buttons.
  • Hybrid first step, with different segments that use different mechanisms; second step with a Template View that inserts the segments in layout hooks.
  • Template View used for both steps, first with a page-specific scripts and then with a generic one.

Usually Transform View is not used in the second step since the data is not fine grained (it is an array of strings basically), and thus cannot be used proficiently from a programmatic approach anyway.

Layouts

The most common incarnation of this pattern is a layout implementation, like Zend_Layout. In the case of this framework's component, the pattern works as follows:

  • first, different views are rendered (template View); every one targets a different segment, named accordingly. The main one, rendered automatically, is named 'content'. Other ones (custom) can be for example a breadcrumb section or a Where I am now box.
  • The second step renders the layout (another Template View), which submerges the segments in additional HTML and prepares a page. Common elements (of the layout, in fact) are added here: footers, headers, navigation bar.Note that both views are not static HTML but PHP code, and thus they can be dynamic: for examples the layout can contain menus that highlight the current choices.

In complex cases, the layout view can even change: for example you can have one view with the lateral menu in some sections and one without, where space is needed for galleries and similar pages; here the include() construct is crucial to factor out common elements between the different layouts. Keeping the views as simple PHP scripts open up the possibility of using the classical tools for page composition like include() and readfile().

The intermediate representation here is a map of strings, where the keys are the names of the segments and there is a conventional one for the main one (content), which is populated by a view automatically rendered.

When multiple representations like XML feeds or JSON are supported, they must be supported in both the steps. The simplest thing to do is usually disable the second step when producing such representations.

Advantages

With a Two Step View, every page looks the same easily; there is no duplication of generic HTML code. Every modern web site or application uses a solution to factor out common elements like headers and footer (or even the <head> section). A Two Step View is the modern version of include header.php which were scattered across the different pages, which in Front Controller approaches have been substituted by a layout. The control is inverted: it is the single entry point which has the control, while the single pages have now no responsibilities of inclusion (and they become simpler as a result.)

Disadvantages

Like with every pattern, there is some added accidental complexity in using it if not necessary. However, you can find a framework which handles it for you easily. It's a very diffused pattern and for PHP frameworks is a must-have feature.

Maybe you have too few pages to strictly need this pattern, or you have a single one which is built via Ajax on the client side (a la GMail). Or, maybe you want graphical freedom and to make every page radically different from the others. Here a layout can't factor out any "commonalities"; don't see duplication where there isn't.

Examples

The sample code continues on the theme of Zend_Layout, and shows the different views necessary to make this pattern work in Zend Framework.

Here's an example of first-step Template View:

<?php
// views/scripts/article/index.phtml (first step)
$this->headTitle($this->article['title']);
?>
<div class="content_article">
    <div class="author">
        <?php printf($this->translate('Posted by %s'),
                    '<a href="' . $this->url(array('slug' => $this->article['author']['id']), 'user') . '">' . $this->article['author']['nick'] . '</a>');
        ?>
    </div>
    <div class="date">
        <?php printf($this->translate('On %s'), 
                     $this->date($this->article['created_at']));
        ?>
    </div>
    <div class="image">
        <?php echo $this->preview($this->article['image']); ?>
    </div>
    <div class="description">
        <?php echo $this->article['description']; ?>
    </div>
    <div class="text">
        <?php echo $this->article['text']; ?>
    </div>
    ...
</div>

This is the second-step Template View:

<?php
// layout.phtml (second step)
echo $this->doctype(Zend_View_Helper_Doctype::XHTML1_STRICT); ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php
echo $this->headTitle($this->info('name'))
->headTitle('Powered by Ossigeno ' . Otk_Version::VERSION)
->setSeparator(' - ');
echo $this->headMeta()->appendName('name', $this->info('name'))
->appendName('description', $this->info('description'))
->appendName('keywords', $this->info('keywords'))
->appendName('author', $this->info('author'))
->appendName('generator', 'Ossigeno ' . Otk_Version::VERSION)
->appendName('reply-to', $this->info('admin'))
->appendHttpEquiv('Content-Type', 'text/html; charset=utf-8');
echo $this->headLink(array(
'href' => $this->info('graphicBase') . 'favicon.ico',
'type' => 'image/x-icon',
'rel' => 'icon'
))
->appendStylesheet($this->info('graphicBase') . 'reset.css')
->appendStylesheet($this->info('graphicBase') . 'style.css');
echo $this->dojo()
->addStylesheetModule('dijit.themes.tundra')
->requireModule('dijit.Editor');
echo $this->headScript();
?>
</head>
<body class="tundra">
<?php echo $this->render("{$this->template}/header.phtml"); ?>
<?php echo $this->layout()->content; ?>
<?php echo $this->render("{$this->template}/footer.phtml"); ?>
</body>
</html>

While these are the building blocks referred to in the layout:

<!-- header.phtml -->
<div id="container">
<div id="header">
<div id="logo"><a href="<?php echo $this->url(array(), 'default', true); ?>"> </a></div>
<?php echo $this->cachedBlock('header'); ?>
</div>
<div id="menu">
<div id="flash_messenger">
<?php echo $this->layout()->flash_messenger; ?>
</div>
<div id="admin">
<?php echo $this->layout()->admin; ?>
</div>
<div id="navigation">
<?php echo $this->layout()->navigation; ?>
</div>
<?php echo $this->cachedBlock('menu'); ?>
</div>
<div id="content">
<? if($this->headTitle()): ?>
<h1><? echo strip_tags($this->headTitle()); ?></h1>
<? endif; ?>
<a accesskey="2"></a>
<div class="data">
<!-- footer.phtml -->
</div><!-- close .data -->
</div><!-- close #content -->
<div id="footer"><? echo $this->cachedBlock('footer'); ?></div>
</div><!-- close #container -->
Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}