Categories


Loading feed
Loading feed

View Helpers in Zend Framework


Views in MVC are allowed to communicate with the Model (using read-only operations), and are allowed to perform display-related logic. That said, how do you actually access the model? And what if you have some fairly complex logic that you may need to repeat, or which you may not want to display directly in the view in order to keep it clean and easy to read? In Zend Framework, the answer is to use View Helpers.

What is a View Helper?

A View Helper is simply a class that follows particular naming conventions, When attached to a view object, you can call the helper as if it were a method of the view object itself. The View object retains helper instances, which means that they retain states between calls.

Common use cases for view helpers include:

  • Accessing models
  • Performing complex or repeatable display logic
  • Manipulating and formatting model data
  • Persisting data between view scripts

Creating and Registering a View Helper

The naming conventions are simple. The class must end with the name of the helper in MixedCase, and contain a method named after the helper using camelCase. As an example, if we had a helper named 'FooBar', we'd need a class that ended in 'FooBar' with a method named 'fooBar'. View Helper classes need a class prefix as well; they can't just be named after themselves (more on this later). We'll give our helper the prefix 'My_Helper', and it might look like this:

class My_Helper_FooBar
{
    public function fooBar()
    {
    }
}


Following the ZF coding standards, let's save that in our library as My/Helper/FooBar.php.

Now we need to register the view helper with the view object. Zend_View doesn't allow you to register individual helpers with it, but you can specify paths to helpers. We'll do this by calling the addHelperPath() method, which takes the arguments $path and $prefix:

$view->addHelperPath('My/Helper/', 'My_Helper');


You can now call this helper as if it were the fooBar() method of the view object:

$view->fooBar();


Now, what if you need access to the view or other helpers from within your helper? If you specify a setView() method in your helper, Zend_View will then inject the current view object into the helper after instantiating it. Let's modify our helper to add this functionality:

class My_Helper_FooBar
{
    public $view;

    public function fooBar()
    {
    }

    public function setView(Zend_View_Interface $view)
    {
        $this->view = $view;
    }
}


Now it's time to ask yourself what the helper should return. By convention, your helpers should never actually echo any content; they should return it. Most helpers will do precisely that -- returning content. However, a number of helpers will return instances of themselves -- which allows you to then call additional methods on the helper class. This is potentially useful when you need to be able to provide state information, or have a group of related functionality but don't need or want multiple helpers to accomplish it. Finally, you may opt to return nothing, and instead simply perform an operation; one example might be logging page requests.

In our example, we'll modify the helper to accept a "name" argument, and then simply return the string 'fooBar ' plus the name; we'll escape the name using the view object's escaping mechansims. Not terribly helpful, but it helps illustrate the concept:

class My_Helper_FooBar
{
    public $view;

    public function fooBar($name)
    {
        return 'fooBar ' . $this->view->escape($name);
    }

    public function setView(Zend_View_Interface $view)
    {
        $this->view = $view;
    }
}


Factoids on Helper Paths

Now, a few notes on helper paths and prefixes. First, the path and class prefix need not have a one-to-one relationship as might your other library code; what is important, however, is that all classes in that directory have that class prefix. Zend_View will merely check to see if a class file with the helper name exists in that directory, and, if not, move to the next directory in the stack. This means you can group your helpers in one or more directories, then register these paths with the view object once -- and have access to all helpers in those paths.

Second, we mentioned earlier that you need class prefixes. The reason is to prevent collisions between libraries and standard class names. This also allows you to extend existing helpers to provide additional functionality (e.g., the partialLoop() helper extends partial()).

Third, building on this last point, helper paths, as is the case with all plugin paths in ZF, operate as LIFO stacks. This allows you to create helpers with your own class prefix and have them override helpers of the same name with different prefixes. In other words, it allows you to override the behavior of the standard helpers shipped with ZF.

Building on this last point, let's say you don't like how the formHidden() view helper works; you want it to include an extra class always -- so that you can easily pull all hidden elements using javascript to perform an operation on them. Overall, the base functionality is fine, so you might do something like this:

class My_View_Helper_FormHidden extends Zend_View_Helper_FormHidden
{
    public function formHidden($name, $value = null, array $attribs = null)
    {
        if (null === $attribs) {
            $attribs = array('class' => 'hidden');
        } else {
            if (array_key_exists('class', $attribs)) {
                $attribs['class'] .= ' hidden';
            } else {
                $attribs['class'] = 'hidden';
            }
        }
        return parent::formHidden($name, $value, $attribs);
    }
}


You could then add a path to this helper to your view object:

$view->addHelperPath('My/View/Helper/', 'My_View_Helper');


and then, any time formHidden() is called, your new helper will be called.

In summary, helpers are a great way to extend the functionality of Zend_View -- and also a great way to customize and extend other helpers themselves!

Standard View Helpers

So, now that we've covered how to create view helpers and register them with the view object, you might be wondering what view helpers are available. The answer is, actually, quite a few.

The standard view helpers shipped with ZF 1.5 fall into roughly three categories: form helpers, placeholders, and utility helpers:

Form Helpers Placeholders Utility
fieldset()
form()
formButton()
formCheckbox()
formErrors()
formFile()
formHidden()
formImage()
formLabel()
formMultiCheckbox()
formNote()
formPassword()
formRadio()
formReset()
formSelect()
formSubmit()
formText()
formTextarea()
htmlList()
placeholder()
doctype()
headLink()
headMeta()
headScript()
headStyle()
headTitle()
inlineScript()
action()
declareVars()
json()
layout()
partial()
partialLoop()
translate()
url()

I won't go into what each does; most are self-explanatory, and all are documented in the manual. However, several of these are worth mentioning specifically.

Partials

Zend_View has the method render(), which allows you to render a view script. However, every successive view script rendered by the view object will have the entire variable scope of the script preceding it. This can lead to some awkward issues when you want to re-use a script over multiple iterations, or when you want to manipulate view variables (which could have consequences for later scripts).

Enter partials. The big difference between rendering a partial and a normal view script is that a partial gets its own variable scope; it will only see the variables passed to it directly. As an example, take the following call to a partial:

<?= $this->partial('foo.phtml', array('foo' => 'bar')) ?>


Were we to call the foo.phtml script normally, it would inherit all variables in the view. However, calling it as a partial, as above, it now only receives a single variable, 'foo', with the value 'bar'.

Where might this be useful? One particular case is for looping through result sets from your model. In fact, this case is so common that an additional, slightly more performant, helper was created, partialLoop(). This helper allows you to pass a result set, over which the helper than iterates, passing the individual result to the specified partial.

Let's say we had a Zend_Db_Table_Rowset stored in the results member of our view. Each item in the rowset is a Zend_Db_Table_Row; this will be passed to our partial, which will then be able to use the fields in the row as if they were view variables. If our table had the fields 'username' and 'email', we could then create a partial like the following:

    <li><a href="mailto:<?= $this->escape($this->email) ?>">
        <?= $this->escape($this->username) ?></a></li>


Our view script would then call it in the following context:

<ul>
    <?= $this->partialLoop('results.phtml', $this->results) ?>
</ul>


Partials allow us to operate in a clean scope and separate out useful tidbits of code for re-use in our applications. Additionally, you end up with cleaner, more concise application view scripts that contain less logic and maintain better readability.

Placeholder Helpers

The basic premise around placeholders is persisting content between views. Within Zend Framework's MVC, in most cases placeholders do not make sense; since the same view object is utilized between all controllers and the layout by default, you can simply utilize view variables as a persistent storage mechanism. So, why bother with a specific placeholder implementation?

There are actually several reasons. First off, not everyone uses the same view object, opting instead to use a separate view object for each discrete rendering task. Second, and more commonly, developers are using partials -- which have their own variable scope. The only way to persist information from a view script to a partial will be with a placeholder (or by explicitly passing the information into the partial). Third, some placeholders are used to provide defaults and hinting to other helpers -- the doctype() helper is a prime example of this. Finally, placeholders offer the ability to aggregate and capture content for later use.

Let's look at some of these points in more detail.

Doctype

The doctype() helper allows you to specify the HTML DocType for the final output. This helper is then consulted by many other helpers to determine how to perform markup; for example, <link> tags do not need to be closed in HTML4, but in XHTML1, they do. If you wanted to make your own helper DocType aware, you could then add a check as follows (assuming you've allowed for the view to register itself with the helper):

if ($this->view->doctype()->isXhtml()) {
}


For this to work, however, you have to tell the doctype helper what DocType is being used before making such calls; otherwise, they will use the default (which is HTML4 transitional). Add the following call to your bootstrap or an early-running plugin:

$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
$viewRenderer->initView();
$viewRenderer->view->doctype('XHTML1_STRICT');


In your layout, simply echo the doctype:

<?= $this->doctype() ?>


Capturing Content

Oftentimes, you need to generate content within an application view to inject elsewhere in the layout. How can you both generate the content dynamically and push it into a placeholder?

The answer is simple: placeholders support capturing. So, as an example:

<? $this->placeholder('foo')->captureStart() ?>
Welcome, <a href="/user/profile/<?= $this->userId ?>">
    <?= $this->username ?>
</a>
<? $this->placeholder('foo')->captureEnd() ?>


You can then echo out the placeholder later, and the values will have been filled in:

<?= $this->placeholder('foo') ?>


While this is useful in and of itself, it's even more useful when combined with the following topic: content aggregation.

Content Aggregation

Placeholders actually use a storage container that extends ArrayObject. This gives them some incredible features and offers some flexibility that wouldn't be possible if they were simply stored as values.

As an example, let's say you're building a sidebar for your site, and it will consist of one or more blocks of content. The sidebar itself needs to be wrapped in a particular div element, and expects to have child div elements contained within it. The actual blocks, however, are dynamic and will be determined by the current user, the page they are on, and the section of the site they are in.

Since our placeholders are basically glorified arrays, we can actually append new values into them easily. This allows us to persist the sidebar between view scripts and then add to it. Additionally, we've built in the ability to specify content with which to surround and separate the items aggregated in the placeholder.

Let's setup the basic sidebar defaults:

<? $sidebar = $this->placeholder('sidebar');
   $sidebar->setPrefix('<div id="sidebar"><div class="sidebar-item">')
           ->setSeparator('</div><div class="sidebar-item">')
           ->setPostfix('</div></div>');
?>


What the above does is create a new placeholder, 'sidebar'. We then set the text with which to begin the output to be the sidebar div, with a child div of a sidebar-item. We separate multiple items in the sidebar by closing off the previous sidebar-item div and starting a new one. Finally, we close the last child div and the sidebar div.

If you're a stickler for the presentation of your HTML, you can also indicate indentation; indentation will be prepended to all items rendered in the placeholder:

<? $this->placeholder('sidebar')->setIndent('    '); ?>


Note: the above settings can be done any time prior to rendering the placeholder.

Now, let's say that we want to display a box showing login status; if the user is logged in, we display their name; if they are not, we display a login button. We'll run this from a plugin that runs at dispatchLoopStartup():

$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
$viewRenderer->initView();
$view = $viewRenderer->view;

if (Zend_Auth::hasIdentity()) {
    $username = Zend_Auth::getIdentity()->username;
    $content = 'Welcome back, <b>' . $view->escape($username) . '</b>';
} else {
    $content = '<a href="/login">Login</a>';
}
$view->placeholder('sidebar')->append($content);


Our application view script might then want to display a set of links. We'll utilize capturing to grab these and set them in the sidebar. By default, capturing appends to the placeholder, which is exactly what we want to do.

<? $this->placeholder('sidebar')->captureStart() ?>
<ul>
    <li><a href="/foo">Foo</a></li>
    <li><a href="/bar">Bar</a></li>
    <li><a href="/baz">Baz</a></li>
</ul>
<? $this->placeholder('sidebar')->captureEnd() ?>


In our layout script, we can echo the placeholder:

<?= $this->placeholder('sidebar') ?>


Which will generate the following output:

<div id="sidebar"><div class="sidebar-item">
    <a href="/login">Login</a>
</div><div class="sidebar-item">
    <ul>
        <li><a href="/foo">Foo</a></li>
        <li><a href="/bar">Bar</a></li>
        <li><a href="/baz">Baz</a></li>
    </ul>
</div></div>


What if you want to prepend an item to the placeholder? or set it at a particular index? No problem -- use the prepend() or offsetSet($index, $content) methods. If you want to limit your placeholder to a single item of content, use the set() method.

Special Placeholder Implementations

There are a number of placeholder implementations contained in the standard distribution. All of these have the same base functionality as the standard placeholder() helper, but also extend it. Primarily these are used for providing information to the layout script in the form of aggregated <head> content -- the page title, scripts, and stylesheets.

For more information on these, you may want to take a look at the following resources:

Final Word

View Helpers are at heart fairly simple and trivial to implement and utlize. However, they can add some extremely rich support for your display layer, help you keep your view scripts clean and manageable, and isolate and encapsulate often-used logic -- allowing you to adhere to the DRY principle. If you haven't been using helpers, or if you've only been using a subset of them, give them a try!

And, in case you missed the previous articles:

Comments


Tuesday, April 29, 2008
STILL A LITTLE UNSURE...
10:37AM PDT · mabrin [unregistered]
RE: VIEW HELPERS ACCESSING MODELS
1:36PM PDT · weierophinney
Friday, May 2, 2008
GREAT
1:40AM PDT · mabrin [unregistered]
Tuesday, May 6, 2008
$THIS... REDUNDANCY?
1:38AM PDT · philip142au
Tuesday, May 13, 2008
RE: $THIS... REDUNDANCY?
4:36AM PDT · weierophinney