Several months ago, Andi
asked me to take the role of lead developer on a refactoring of the
Zend Framework MVC components. I
agreed, though somewhat reluctantly; I already maintain
another MVC library, and wasn't
sure how well I could fill the shoes of people like my friends
Mike, who had done the initial
development on the controller classes, and
Paul, who provided Zend_View.
The experience has been incredibly rewarding, however, and I've had the
chance to pick the brains of and work with some top-notch developers in the
process. In the next week or so, we'll be releasing version 0.6.0 of the
framework, and it will include much of my work in the MVC components as part
of the core distribution. A big thanks to all those who have contributed
opinions, design help, code, tests, and documentation; another thank you
goes to Andi for trusting and supporting me in this endeavor.
So, what are the changes? Read on to find out...
Most of the changes lie under the hood at this point; the most basic usage
of the MVC components remains the same. The changes introduced mainly
promote greater flexibility and testing. In fact, the most significant
changes, the introduction of request and response objects, are transparent
to most users, but are the very changes that make unit testing of the MVC
components both possible and easy.
So, as you may have guessed, the controller classes are now unit tested;
coverage isn't perfect, but it's reasonable (currently around 68% code
coverage), and an improvement over the basically zero coverage from before
(only Martel's RewriteRouter had tests before I started). What this means to
developers is that from this point forward, any changes to the controller
classes will need to pass regression tests... and those tests actually
exist. It also means that it is now possible to unit test applications
without needing a web server; this is going to be a big step for MVC
applications based on the framework.
By introducing the request and response objects, we now no longer need to
rely on a web environment in order to make requests; having a response
object makes it trivial to capture output from the various action
controllers and then test against expected results. This is a huge change.
Additionally, by de-coupling the controllers from the request environment,
it becomes possible to use the MVC components in non-web environments. Think
CLI and PHP-GTK.
Another change is that a router is no longer needed. This allows developers
to use the MVC components in non-web environments, where routing may not
actually be necessary, as well as to use the MVC components in web
environments where pretty URLs are difficult to configure. IIS, for
instance, doesn't have a mod_rewrite equivalent out-of-the-box, so being
able to specify a url such as
http://localhost/index.php?controller=index&action=view and
have it dispatch properly is a nice feature.
A feature that many were requesting was the ability to push parameters into
the front controller, and have those push through to each of the router,
dispatcher, and action controllers. Such an ability would obviate the need
for a registry, and also allow the entire controller chain to share an
environment. I made use of this today when a request came in for the ability
to specify an optional module parameter in the request URI; instead of
breaking backwards compatability or creating new routers and dispatchers, I
was able instead to simply push a 'useModules' setting through the chain,
and if discovered, act on it. (This new feature allows urls such as
http://localhost/module/controller/action to dispatch to
Module_Controller::actionAction(); think controller classes in
subdirectories.)
One feature that I think a lot of people don't understand is the Response
object. It is a container for the entire response generated by a request,
whether that's from a single or multiple actions. As such, it also
aggregates exceptions from the process. It's final purpose is to return that
response to the client, and this is done by simply echo()ing it; its
__toString() method should take care of any final rendering to perform.
The basic response object provided with the new system does very little. It
allows for the setting and aggregation of content through its setBody() and
appendBody() methods, as well as setting headers via setHeader(). If
exceptions occur, they are registered via setException(). The suggestion
made in the documentation is to use appendBody() to aggregate content, and
then have __toString() return the aggregated content en masse by echoing the
response at the end of the dispatch loop: echo $front->dispatch().
While this is nice, there are some much more interesting things you can do
with it. In a recent project I did, I integrated Zend_View and Zend_Json in
the response object, and added accessors in my action controllers to push
content to the response object. Then, based on the request, I could switch
between returning JSON strings or XHTML content. If an exception occurred, I
could redirect to an error page, or, in the case of an AJAX request, return
an error encapsulated in a JSON string. This type of context switching is
very powerful, and I'll blog more about how it can be achieved later.
If you already use the framework MVC but haven't tried the new code, I
encourage you to download a snapshot
or grab it from subversion
and give it a spin; there's
a document covering migration,
and any feedback or additions on this would be greatly appreciated (this
document in the framework wiki is currently out-of-date; check the docbook
in the framework distribution you download for more current information).
If you haven't tried the framework MVC, and are interested in MVC libraries,
give it a whirl and let me know what you think!