How Do You Zend_View? I'll Show You Mine...
An interesting design decision made by the Zend Framework team was, well, not to make too many decisions about how the View portion of a Zend Framework MVC application should be implemented. You can use plain PHP to generate markup, use a templating library such as Smarty or Savant, or go your own way entirely. At work we've used both Wall4PHP, and subsequently a custom component-based solution, for example.
The flexibility is great then, but with freedom comes responsibility. Perhaps coincidentally, view scripts tend to be the part of a ZF app that attract the most cruft, the most untestable code, and the most mingling of concerns (though controllers certainly give them a run for their money).
Since someone recently asked, I'll show how I've implemented views on pointbeing.net. I don't claim that the approach is awesome, or even suitable for every case, but it seems to work for me. In return I'd very much like to hear or see how others implement their Zend_Views.
The Controller
It's hard to talk about Zend_View
without starting with the controller, since in practice a Zend Framework controller decides which view to render, either explicitly or implicitly.
I strongly prefer things to be explicit, and so I'll always disable auto rendering. This works out just fine, since views rarely correspond one-to-one with controller actions (in my experience, if they do, it tends to mean that one is either doing something wrong, or is building something that isn't terribly interesting).
You can disable auto rendering easily. One option is to add this line to a controller action:
<php
$this->_helper->viewRenderer->setNoRender();
You obviously don't want to have to remember to add that to every action, so that line sits well in a controller's init()
method. Since my controllers all extend a common base controller, the init()
method of the parent controller makes for a particularly convenient place for that code to live.
All that remains is for the controller to decide which view should be rendered, and tell the framework. That's easily done like so:
<php
class My_IndexController extends My_BaseController
{
public function indexAction()
{
// ...control logic omitted for brevity...
$this->render('index');
}
}
The string "index"
is mapped to a physical view script in Zend Framework's default manner. The real Zend_View
activity kicks off in that view script, so let's have a look inside it.
View Scripts
My physical view scripts are deceptively simple, and contain the minimum of logic. This is by choice, as typically you have to hold your breath before opening a ZF MVC view script, and abandon hope shortly after. Whilst a lot of the examples here are pseudo-code, this one is basically lifted directly from the page you're viewing now:
<php
/**
* Blog item page view script
*
* @author Simon Harris
* @package pointbeing.net
* @subpackage views
*/
$this->title = $this->post->title;
echo $this->render('_shared/header.phtml');
echo $this->render('_shared/_sidebar.phtml');
echo $this->render('weblog_item/_item.phtml');
echo $this->render('weblog_item/_social_bookmarks.phtml');
echo $this->render('_shared/_ads_banner.phtml');
echo $this->render('weblog_item/_comments.phtml');
echo $this->render('weblog_item/_commentform.phtml');
echo $this->render('_shared/footer.phtml');
Hopefully that's quite self-explanatory. Each of those .phtml
files is what I think of as a page fragment, and they're little more than HTML templates. It's time to have a look at how those work.
View Templates
Here's an example of the contents of one such .phtml
file. This is roughly the code used to render the list of comments below a blog post:
<div class="content" id="comments">
<h3>Comments</h3>
<?php foreach ( $this->post->getComments() as $comment ) : ?>
<div class="comment">
<p id="comment_<?php echo $comment->commentid; ?>">
<strong>
Posted by <?php echo $this->commenterName($comment); ?>
on <?php echo $this->formatDate($comment->getTimestamp()); ?>.
</strong>
</p>
<p><?php echo $this->renderBlogComment($comment); ?></p>
</div>
<?php endforeach; ?>
</div>
Perhaps the most salient aspect of those files is that they are markup. We're out of PHP mode and very much in full-on front-end HTML mode now. This is exactly the kind of separation of concerns that I want to see in PHP applications.
It's also worth noting that I don't use any kind of templating library like Smarty. I can see why people feel the urge to do so, but it's not an urge by which I'm afflicted. PHP works just fine for this purpose.
I do, however, use the "alternative" control flow syntax for PHP. That is, things like foreach
/endforeach
and if
/endif
. I find that this separates template code from application code a litle further, and experience has shown that it's easier for front-end specialists and designers to work with than several levels of nested curly braces. (Needless to say, I don't have the luxury of having staff to work on the site for me, but it's a nice thought).
It turns out that some parts of view logic require code that's a bit knottier than HTML and a handful of loops can achieve, so this is where View Helpers come in.
View Helpers
View Helpers are one of those magic little features that one finds dotted around Zend Framework. They're designed for exactly this purpose: avoiding embedding complex display logic in HTML templates.
In the template code above, you can see calls to $this->commenterName()
, $this->formatDate()
and $this->renderBlogComment()
. Given that $this
is still an instance of Zend_View
, which doesn't have those methods, what gives?
Well, those method calls are caught by some __call()
magic in Zend_View
, and mapped, via a series of naming and file-location conventions to dedicated methods on dedicated classes called View Helpers, which must return a string that can be echo()
d. Here's the View Helper responsible for handling the formatDate()
call:
<php
class My_View_Helper_FormatDate
{
/**
* @param int $timestamp
* @return string
*/
public function formatDate($timestamp)
{
return date('l, \t\h\e jS \of F, Y', $timestamp);
}
}
Of course, they get a lot more complicated, with all sorts of loops and regexes and whatnot - you don't even want to see the one that renders the blog post itself! - but hopefully you can see straightaway how nice it is to take that formatting logic out of the template, and how handy it can be to have a little library of rendering helpers that you can call upon anywhere in your template code.
Getting Data into Views
One issue which I've avoided so far is how to get model data into a Zend_View
, after all, that $this->post
in the third code snippet above didn't get there by magic.
I must admit that my codebase is a little schizophrenic in this department. I started off taking a very common and seemingly uncontroversial route of having the controllers put it there. There was, and still is, quite a lot of this kind of thing in controller actions:
<php
class My_ItemController extends My_BaseController
{
public function indexAction()
{
// the item key from the URL
$key = $this->_request->getParam('key');
if ( !$post = $this->_findPost($key) ) {
// forward to ErrorController
return $this->_bail();
}
$this->view->post = $post;
$this->view->postedin = $this->_findCategories($post->id);
$this->view->tags = $this->_findTags($post->id);
$this->render('index');
}
}
You'll recognise that as being a fleshed-out version of the listing we began with. I described this approach as "seemingly uncontroversial", since it's very common, and it's even how the Framework documentation does it. I felt comfortable with this since I didn't like views knowing how to "get" data. Having the controller fetch the data and inject it into the view seemed to resolve the issue nicely.
However, and as is so often the case, several furious discussions with Ciaran led to the inevitable conclusion that I was, at least in part, missing the point.
Everyone understands that a key motivation behind MVC is the separation of data modelling, application flow logic and display concerns, but what's often overlooked is something Fowler points out in his seminal Patterns of Enterprise Application Architecture: the separation of read versus write behaviour.
Views take care of reads, while controllers take care of writes. The view should therefore feel free to "know about" the model. In fact, MVC originated in GUI programming, where the view would traditionally be pretty tightly bound to the model, typically though use of the Observer pattern.
I'm comfortable with the controller fetching the key resource around which the page is based - in this case a blog post item, since it's the controller that has to deal with situations where a non-existent post is requested.
However, I am now doing a lot less of that "pre-population" that you see in the controller code, and instead adding object-traversal accessors to Models. So for example a blog post model object has methods such as getComments()
and getTags()
that the view can call without caring about where the data behind them originates.
It seems that we're now moving quickly into Model territory, so that's probably a good point at which to end this whirlwind tour of my site's Zend_View
code.
Conclusions
Well, as I mentioned, it works for me. There are some genuine practical benefits that have arisen from this approach:
- Pages are composed from smallish, self-contained and reusable fragments; therefore
- New pages can be added to the site very quickly indeed
- There's a very real separation of concerns between application logic and display logic
That third point is particularly gratifying, since "separation of concerns" is one of those holy grails that everyone pays lip service to, but very rarely actually achieves.
I'll end by reiterating the point that I don't claim this to be perfect, and in fact we do things very differently at work, where we have very different problems to solve. Which is why I'd be particularly interested to see: how do you Zend_View
?
Russell
At work we use Smarty, which I find tedious and badly documented. Interestingly I was the only one who was against using it in our team though.
Ciaran McNulty
Really interesting stuff (and thanks for the namecheck).
If you're finding you're doing a lot of includes of headers, footers and so on in your views it's really worth looking at Zend_Layout.