Regular readers have had a gentle (I hope!) introduction into the world of Complex Views over the course of the last 5 parts of this ongoing series. We've started with an overview of Zend_View and its shortcomings in assembling web pages composed of many reusable, and even nested, elements. Over the previous 4 parts we delved into some theory, described a few solutions, and learned that I need to unit test my examples more exhaustively . Everyone's a winner here at Astrum Futura, even me!
Part 6 now takes our previous fragments of theory and attempts to stitch them together into a cohesive whole - a description of a possible end solution. Yes, I know code talks loudly, and we'll get there with an almighty bang in Part 7 (which I promise is no more than a week away). Until then it's more verbosity.
So let's get stuck in!
Half the trouble in conceiving of a extended View system is agreeing on terminology. Everyone has their own idea of the basic concepts, but without names we're left with vague descriptions. Here I'll throw out some terms, some borrowed, others mangled slightly, the rest fairly obvious. These terms all describe specific rendering processes. Methods of capturing presentation logic in neat parcels which carry specific consequences, follow object oriented practices, and provide (we dare hope) commonly sought functionality.
The full list (and please feel free to add/suggest changes to these in the comments or by email) is:
The mighty Include is the mainstay of rendering in PHP. Smarty has the {include} tag, Zend_View allows for template inclusion using secondary Zend_View::render() calls, even stark naked PHP is a master of the Include using the aptly named include() function.
Includes are simple to understand - they drag additional templates into the same scope as the calling template at some specific location. In essence, all variables the calling code has access to, are instantly available to the included file. This does however add a little smell - all includes are almost by definition tightly coupled to the calling code. It's stitched into the fabric of any class that uses include(), such as Zend_View::run(), with nary an interface in sight.
Partials are not dissimilar from Includes, except that they have one very specific characteristic. They are decoupled from the calling class. This can be managed by assigning each Partial to it's own respective independent View object. The relationship is a typical Parent->Child, where the Parent View creates a new Child View (one template per View mind) wherever it uses a Partial.
Because both are independent entities, the Parent View must provide all necessary data to the Child to enable rendering, usually when the Partial is rendered. If you can follow this ingeniously vague description, it's the basis of the Composite View Pattern I discussed previously.
Partials also have a second characteristic. You can dynamically change their context when rendering. Imagine creating a list of articles on an HTML page. Your primary View renders everything except the article list. At that point, it iterates across a list of article data (the Model) and calls the same Partial each time, passing it each article Model in turn. The Partial will simply render the same template over and over, but applies the new data each turn.
Example Partial called in a foreach loop with a new context each time:
As I've noted before you can call these "Glorified Includes" since they are so similar. The main difference is that handling Modules is managed externally either through convention or configuration. Unlike Zend_View::render() there's no path definition required. Also the many benefits of an independent View object (nested Layouts, Placeholders, etc.) are all intact.
If you're watching the code, these samples are suggestions of an interface. In Part 7, we'll define something concretely with "real" code . Above we're employing a convention the templates names starting with an underscore are special in that they are not intended for rendering as a primary View.
Dispatches
The Dispatch rendering process is a heavy weight. It's purpose is to generate a full bore Controller dispatch using a customised request. It can be used similar to a Partial or Include, but requires a full dispatch cycle to a Controller and Action. As a result it has several possible flaws - it depends on the presence of a suitable Controller component (i.e. Zend_Controller) which means it's not likely portable, it will suffer performance wise from all the extra work required to dispatch a request, and it has very few advantages over a Partial.
One possible reason for using it is if a View has a dependency on some authorisation/authentication system like Zend_Auth and Zend_Acl. Even here though I would still recommend using a View Helper if feasible to query both. Another potential use if the View is being rendered from an external source (e.g. integrated third party application). So while it's imperfect, it's still quite useful to have available.
Dispatch and echo the rendered view from ArticleController::adminlinksAction():
The subject of Part 5 of this series. Layouts define common markup which will encapsulate any number of Views. While it's easy to use Includes to add Layouts, you would have to copy the Include code to every single template (the traditional include header/footer code). This is hardly the most maintainable method of doing things. What if some Views suddenly need a different header? Will you run off to edit 100+ templates? For this description, Layouts are "secondary" templates, into which the main template/View being rendered is implanted before being echoed to a client.
The funny thing about Layouts is how obvious and useful they are once you figure out what they really do. Not only can they be used as an overall template for presentation of a full HTML page, they can also be used to capture the layout of subsections or other nested elements. Because Layouts are a top-level application setting, they are simple to manage and assign to different groups of Views with the right setup.
For this discussion we need to make one assumption for later. Layouts are always the last rendering step when processing any View. This allows nested template the opportunity to make subtle changes to the Layout before it's rendered. We'll find a use for this render-order with Placeholders.
In Zend_Controller_Action app specific subclass, or another appropriate place:
A common problem with simple Layout systems is that they cannot capture the full context of a page. How can a Layout know whether a specific View demand additional layout properties like extra CSS files, that once off AJAX script, or a few extra meta elements?
Placeholders define a position where templates can append extra markup if they wish. Since Layouts are rendered last, they can allow for other templates of the primary view to make such additions. Placeholders can also be used to define default markup for rendering, unless overridden by a template, or unless some condition is met.
A really simple example is a page composed of a Layout, and several nested Views. If one View presents a blog page, the designer may want to append a element in pointing to the local RSS/Atom feeds. It's only shown for that single page. Using a Placeholder in the application wide Layout would enable this very easily. The blog template could set the Placeholder's value, and the Layout when rendered would insert that into the header dynamically.
Example Layout with a placeholder where templates can append child elements:
A template for displaying a blog which needs to add an RSS link to the section of the Layout:
<?php$this->placeholder()->add('HEAD', '<link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.example.com/rss.xml" />')?> <div id="content"> <!-- Some content to be inserted in Layout --> </div>
Response Segments
Response Segmentation was introduced into the Zend Framework some time ago. It's a Controller driven process which assigns the rendered output of a View to a named segment in the Response object, which is then organised into the full page before it's echoed to the client. In a sense it's not dissimilar from Placeholders, except that the segmentation is not influenced from within the View object tree itself, being delegated to the Response object in Controller instead.
So there you go. 6 terms to keep in mind (pending feedback from you, the Reader) for Part 7. Now that we have these terms, several of which you are acquainted with if following this blog series, some of which are likely new, and some sample interfaces we have opened the gates to defining some starting Unit Tests. I'll spare you the TDD process . Part 7 will elaborate on most of these with cold hard (and colourfully commented) source code.
As always, I value your comments and emails. It's always great to hear of others' experiences in the field.
Rob Allen about Seven Things: Chained To Infinity! Mon, 05.01.2009 07:49 You should come to the PHP Lon
don conference in Feb and then
we could have a drink or two.
..
Regards,
Rob...
D.J. Capelis about Seven Things: Chained To Infinity! Sat, 03.01.2009 06:14 Heh. Looks fun enough. How a
bout I post my reply to this i
n say, two months though... I
wrote part of an entry [...]
Joe Sillitoe about Book Launched! Zend Framework: Surviving The Deep End Fri, 02.01.2009 17:58 Nice work, and I love the para
graph commenting system. I fe
el like you saved me from the
model==database mindset [...]
Christian Kirkegaard about Book Launched! Zend Framework: Surviving The Deep End Fri, 02.01.2009 09:52 Looking forward to reading it
Pádraic! Hope you have some go
od pointers on models and form
s working together