PHP, Zend Framework and Other Crazy Stuff
Archive for April, 2007
Complex Views with the Zend Framework - Part 3: Composite View Pattern
Apr 27th
In the previous two parts of this series of blog posts, I’ve been looking at the task of implementing complex views with the Zend Framework. Part 1 looked at what complex views are, what support for complex views the Zend Framework offers out of the box, and a reference to two design patterns useful in adding further support: View Helper and Composite View. In Part 2, I tackled the View Helper design pattern.
As a brief recap, the View Helper pattern promotes the idea of creating helper classes which allow the View layer of a Model-View-Controller application to directly query the Model layer in a read only fashion - effectively bypassing the Controller layer altogether. In such a way, the pattern offers the option to programmers using the Zend Framework to avoid using the current method of nesting Controller Actions with the Zend_Controller_Action::_forward() method which can be an overly complex approach for parts of a View we know are common to many pages.
In this post, I offer a brief explanation of the Composite View pattern. It’s beyond its scope to show an implementation using the Zend Framework though that’s what I’m building up to accomplish in a later blog entry .
If you haven’t already guessed, the Composite View pattern is related to the Composite Pattern as defined by the Group of Four (GoF). If you’re not familiar with the Composite Pattern, an excellent PHP article for it was published on Zend Devzone and is worth reading.
Here’s a UML diagram of the class relationship although we do things a little different below .
In brief the related Composite View pattern organises View objects into a nested tree structure, in which both parents and children implement some common interface (let’s say render() for now). You can think of each View representing a single element of the overall page, with parents representing sections of the overall page which may contain other component elements. The essential characteristic, inherited from the Composite Pattern, is that the common render() method can be called from the root View, and this method call propagates across all nested objects in the tree until the output from all child Views is finally aggregated into the overall page we intend sending to the browser.
With this description we can make a few assumptions regarding the Zend Framework. The main one being we will need multiple instances of Zend_View to pull it off (as distinct from multiple Controllers). Also all this talk of objects hasn’t explained how the nesting is controlled. We’ll handle the second now, and come back to the Zend_View multiplicity after.
There are two methods of handling nesting of View objects. The first follows a purely object based scheme, where objects are nested and combined at runtime. The second passes control over nesting to our friends - the templates. The template method basically involves adding a function (think of the Composite Pattern’s interface for “composites”) to include another template. Here we’ll call it “attach”, though it could be anything you prefer. We’ll look at this approach below since it’s the simplest one to follow if you’re familiar with Zend_View templates. In addition, the template approach is probably the easiest for programmers and designers to handle since it allows the View layout to be put in place around all these composite View includes.
Now, we may appear to have come full circle here - this sounds suspiciously like a glorified “include” statement but there are differences. The main one being each composite “attach” creates a new View object which can be sourced from other Modules, and with Module specific helpers and filters (and of course Model accessing View Helpers!). If we assumed the composite method was “attach” (common Composite Pattern method), a composite template could look like:
articlelist.phtml (part of the imaginary Articles application Module):
[geshi lang=php]attach(‘stdheader.phtml’, ‘default’); ?>
summary ?>
attach(‘underbody.phtml’) ?>
attach(‘stdfooter.phtml’, ‘default’) ?>[/geshi]
overbody.phtml:
[geshi lang=php]
[/geshi]
Here we have two templates, both utilise an imaginary attach() method from the View object to attach new sub Views to the template at specific locations within the layout (you’re right in thinking attach() will handle rendering of the child View). It also allows for a template to attach Views from other Modules of the application if required (and would transparently manage the different View settings for that Module). I’ve assumed the lack of a “module” parameter means the attached View should come from the current Module only. The last overbody.phtml template allows for more dynamic parameters targeting the RSS View Helper utilised by the Blog Module’s last5headlines.phtml template where we’re using the View Helper pattern to grab headlines from the given RSS feed. We visited very similar functionality in my last post.
Of course in all this we haven’t ruled out Controller nesting completely. That too is still possible, even alongside View nesting and in a very similar fashion. Since each element attached is an independent entity you can mix and match what you want to use, even from other Modules. Of course with all this floating around we still haven’t seen usable code yet! Well, that one’s for another blog entry .
Back to the multiplicity issue for a moment, i.e. the presumed use of multiple Zend_View instances. You can guess that Zend_View will need to be subclassed and/or a standard helper class containing the attach() implementation added. There are other issues also. For example, each View object needs to be independently configured before use - i.e. for paths, encoding, etc. In Part 4 of this series I’ll take a stab at adding finer control over creating View objects in a simple reusable fashion.
I’m sure many of you will already suspect the design pattern that will involve .
Complex Views with the Zend Framework - Part 2: View Helper Pattern
Apr 20th
Part two of my ongoing look at the View layer of the Zend Framework turns its attention to the topic of View Helpers. The Zend Framework manual provides a fairly narrow definition of its helpers which indicates they enable complex tasks, like generating form elements, to be extracted out of views into dedicated helpers. Here I’ll try to explain in greater depth the View Helper pattern which is another of those patterns in the J2EE catalog, and which adds to the range of tasks View Helpers are capable of performing.
The simplest explanation is in the separation of layers enforced by the Model-View-Controller pattern. In a typical web application using the Zend Framework, a URI is used by the framework’s Controller architecture to select an action on a Zend_Controller_Action class to execute. This action method will then normally access the application’s Model to fetch any data it requires, operate on that data, and pass a subset of it to the View (for use in rendering templates). It’s clear to see so far, that the View by default relies on the Controller to pass it the data model it needs when rendering templates.
As in my previous post however, the View may also include any number of elements (header, footer, widgets, etc.) common to ALL pages. Many of these may require data of their own.
So what is the problem? Anytime a partial View needs extra data (the View’s Model) it needs to push calls to extra Controllers (following the current practice for the framework) in order to get that Model. This involves yet another complete dispatch cycle, with any number of classes, plugins, and operations involved. Yet in most cases this is completely unnecessary - why not just let the View request data from the Model directly?
In simple terms, the View Helper acts as a middle-man sitting between the View and the Model. It’s job in our scenario is to replace the need to nest Controllers, and give that nesting/layout control back to where it belongs - the View layer. When a View (or partial View) requires Model data not supplied by the current controller, it calls upon a View Helper to go fetch that data independently of any Controller.
Let’s take a simple example. While building a blog, we’ve decided to add the last 5 entry titles from Planet PHP to the bottom of the page - every page in fact. In keeping with promoting reusability we create a generic Zps_View_Helper_Rss class which can consume RSS, and to which we can add extra methods in the future should they be needed for other Views. This is slightly different to the current view helper description in the framework manual - here we can offer a full selection of public methods forming a fuller (read-only) interface to the underlying Model - the RSS XML.
[geshi lang=php]require_once ‘Zend/Uri.php’;
require_once ‘Zend/Feed/Rss.php’;
class Zps_View_Helper_Rss
{
protected $_channel = null;
public function __construct($url)
{
if (!Zend_Uri::check($url)) {
throw new Exception(‘RSS URL is invalid!’);
}
$this->_channel = new Zend_Feed_Rss($url);
}
public function getTitles($number)
{
$titles = array();
$count = 0;
$number = intval($number);
// Zend_Feed_Abstract implements Iterator!
while ($count <= $number && $this->_channel->valid()) {
$titles[] = $this->_channel->current()->title();
$count++;
$this->_channel->next();
}
return $titles;
}
}[/geshi]
Now comes our sample template - one of those recurring Views:
[geshi lang=php]
-
getTitles(5) as $title): ?>
[/geshi]
And not a controller in sight…;)
Of course the Model can equally be running from any datasource including the database, so there’s a lot of flexibility for these View Helper classes. I haven’t done so here, but you can probably still fit them into the current view helper convention with the single public method (in our case rss() just returning instances of the object.
Of course, there really are times when the recommended Controller _forward() (or a viable alternative) can still be useful. Reliance on plugins was suggested on the mailing list, for ACL perhaps. But the point here is that controller nesting is not required except for those exceptional cases where a View Helper just won’t cut it.
Edit: I figured it be save confusion by noting this helper could equally run from a cache of the selected RSS source which is updated separately at regular intervals (hail to cron!). Save the server making the trip on every request .