View inheritance and “blocks” in Zend_View

Tags:

Some frameworks like Django and PRADO have the concept of view inheritance, or a “master page” like in ASP.NET. Essentially it’s a similar idea as Zend_Layout is in Zend Framework, but the difference is that you can define the master/parent view inside the view itself.

For example, in Django {% extends "base.html" %} would make the current template extend “base.html”, and you then you can define blocks that will be placed in placeholder blocks in the parent. In Zend Framework, doing this kind of “blocks” is a bit complicated and it’s a bit difficult to create default content in them as well, which can be optionally replaced with something else.

Since I like the block and inheritance style approach, I decided to see how this can be done in Zend Framework…

The idea

So the basic idea is to be able to tell a view that it needs to put its content into a parent view, into specific blocks. You must also be able to define default content for the blocks, which you must also be able to overwrite from a child view.

A bare bones basic approach

Before going into a proper implementation with some new code, I decided to play around with what you get with a standard ZF package.

As you may know, you can use $this->render in views to render new content. You also have the placeholder helper which can capture some output into a placeholder. These two can be used to create simple view inheritance:

the view script

<?php $this->placeholder('content')->captureStart(); ?>
 <p>
  This goes inside the content area
 </p>
<?php $this->placeholder('content')->captureEnd(); ?>
 
<?php echo $this->render('master.phtml'); ?>

master script (master.phtml)

<div id="content">
 <?php echo $this->placeholder('content'); ?>
</div>
 
<div id="sidebar">
 <?php if($this->placeholder('sidebar') != ''): ?>
  <?php echo $this->placeholder('sidebar'); ?>
 <?php else:
  <p>sidebar default</p>
 <?php endif; ?>
</div>

So the first script could be a view script called by some action in a controller, and the second could be another view script in our view dir. It would basically put the content between captureStart and captureEnd to the #content div, and show “sidebar default” in the #sidebar div.

As you can see, it isn’t exactly nice: The master render call is in the bottom, where it might be easily hidden from sight and the placeholders aren’t very suitable for displaying default data as you can see from the if else endif mess in the other div.

In addition to being a bit messy and confusing, the above approach might not work very well if you nested multiple master pages – a master that also has a master.

But we can make it better! We have the technology. We can make it better than it was. Better…stronger…faster…

A new, better approach

We’re going to need a few new classes to tackle this problem.

We’ll need a view helper for declaring a master view, a view helper for defining and displaying blocks, and a controller helper and a controller plugin for handling the rest of the magic, mainly rendering the master views.

So there we have the four classes we need. Using them is very simple…

First, register the controller plugin and the controller helper in your bootstrap:

//Assuming you've put the helper to CU/Controller/Action/Helper/ in your include path
Zend_Controller_Action_HelperBroker::addPrefix('CU_Controller_Action_Helper');
 
//Assuming $fc is the front controller
$fc->registerPlugin(new CU_Controller_Plugin_MasterView());

Now we’re all set, just the views left…

The master view. Put this somewhere where the view can load it:

<div id="content">
 <?php echo $this->block('content'); ?>
</div>
 
<div id="sidebar">
 <?php echo $this->block('sidebar')->start(); ?>
  <p>sidebar default</p>
 <?php echo $this->block()->end(); ?>
</div>

Important! Note the echo commands in the master – they’re needed for the content to show up in the view, but do not put them in the child view, as shown next:

<?php $this->masterView('master.phtml'); ?>
 
<?php $this->block('content')->start(); ?>
 <p>
  This goes inside the content area
 </p>
<?php $this->block()->end(); ?>

Note that the closing blocks don’t take any parameters. You can alternatively put the block’s name there, but make sure you don’t type it wrong.

So here we have it, view script inheritance and blocks with Zend_View! In the above example, we could have also defined default html for content, or defined some new content for sidebar and it would’ve overwritten it, but I’ll leave that to you.