Discovering the Zend Framework (0.2.0 Preview)
At ZendCon, I heard there was a new version of the Zend Framework being released. The version number is 0.2.0. That does not sound very stable, but I decided to give it a try anyway, something I did not do back when 0.1.0 was released. This was quite an adventure. The new release is incorporating major changes in the MVC framework and quite a few other libraries. Most of the changes in the MVC section are made to improve testability, or so I was told. Since new changes may break a few things, they were placed in the incubator folder until the next major release. This seemed all right to me.
I downloaded the source code for the framework, added the incubator libs and normal libs to the PHP include path (with incubator first, so it takes those in priority) and started playing with it. Actually, I did not get to play too long before I ran into some problems. It seems like the new version relies on some PHP 5.2 only features. There was no warning for this anywhere, or if there was, I did not run into them. Still, this is acceptable. The Zend Framework is meant to be PHP 5 only and should be switching to bleeding edge technology before it gets too large of a user base. Anyway, as a user of *ubuntu, the bundled version of PHP is 5.1.x, so I removed the incubator libs and decided to play with the old versions instead. I was told the changes were mostly internal, so I should be able to live without them and simply switch later.
MVC
I simply followed the documentation to build the skeleton to start from. Everything went very well. Documentation is one of the very strong aspects of the framework. The API documentation is a little poor (as in auto-generated from source code and function headers were also auto-generated), but the end user documentation is great for the most part. Some libraries are under-documented, but I guess this will be resolved in a near future.
Once I had the MVC framework set-up, I decided to build a small form and use some input, just to play with view and such. The Zend_View class is very simple, and yet very powerful. I never really liked template engines like Smarty or others. I think PHP does HTML display good enough, and this is the default behaviour of the View class. It does not do much more than call a PHP script and catch the output. It lets you assign parameters and get them from the template, but it really forces you to separate the display and logic, and suggest good coding practices.
<?php if ($this->books): ?> <!-- A table of some books. --> <table> <tr> <th>Author</th> <th>Title</th> </tr> <?php foreach ($this->books as $key => $val): ?> <tr> <td><?php echo $this->escape($val['author']) ?></td> <td><?php echo $this->escape($val['title']) ?></td> </tr> <?php endforeach; ?> </table> <?php else: ?> <p>There are no books to display.</p> <?php endif; ?>
I took this code sample straight from the documentation. I always liked this alternate syntax when writing conditions or loops in HTML. It simply looks clean. But the very nice part about this template is that a special escape method is defined to escape output. No need to ask yourself which function to use to escape the information. Actually, you can actually still ask yourself that question, because the function used internally by escape can be chosen. I think this is a very good decision on the long run. With all security alerts that come up, if one is actually affecting what ever function is being used, it can actually be redefined to handle a specific case and the entire code base will be fixed.
You probably noticed that the values were accessed as attributes. The Zend_View class allows you to define properties on the fly. I think it simply looks more elegant than calling an assign() method everywhere with a key and value. The view actually does even more. It manages those display helper functions for you. All you have to do is indicate the path where you store your helpers and you will be able to call your own functions in a fashion similar to the call to escape().
Defining the path to controllers, views and helper functions, that makes quite a lot of configuration. It does not matter at all, because all those things can be initialized in your index page, objects can be stored for access using the Zend::registry() function and you no longer need to touch any configuration. This is a very nice aspect of the Zend Framework. Everything is independent. Once you’ve set-up the __autoload() function, you no longer need to worry about including files, paths to scripts or anything. The right controller will be called, the view is managed and everything in between is magic.
Input filtering
After playing with the views, I decided to handle some input. Naturally, I looked at the Zend_Filter_Input class. This was probably the worst decision I made that day. I wasted a whole lot of time to end up figuring out that the testEmail() method was not implemented, which was totally undocumented. I decided to fall back to the ext/filter extension, which was not installed by default in 5.1.x and that I could not get installed using pecl. I had to install 5.2.0, so I did.
0.2.0 incubator
With the incubator issue resolved, I reactivated the include path for it. Bad surprises there. Very bad. Nothing was working any more. Even if most of the changes were actually internal, it seemed like quite a few changes were required in the initialization phase. The worst part was that none of the samples in the incubator documentation had been updated to reflect those changes. I somehow had the feeling that release was pushed out early for ZendCon. So I searched on the web to find some documentation about it. The 0.2.0 version had been released for quite a few days now. Someone, somewhere must have figured out how to solve this. I simply didn’t feel like searching for samples in the unit tests or read too much API documentation to find the changes.
I found this piece of code on some website (I removed some information that could help identify it, because really, it’s a shame it was ever published). The article actually had the mention ‘updated for 0.2.0′, and they seemed to be proud of it. Actually, they might have used the 0.2.0 release without the incubator packages, which really makes MVC the same thing as 0.1.0.
<?php
require_once 'Zend.php';
Zend::loadClass('Zend_Controller_Front');
Zend::loadClass('Zend_Controller_RewriteRouter');
$controller = Zend_Controller_Front::getInstance();
$router = new Zend_Controller_RewriteRouter();
$router->setRewriteBase('/');
$controller->setRouter($router);
$controller->setControllerDirectory('../app/controllers');
// View init
Zend::loadClass('Zend_View');
$view = new Zend_View;
$view->setScriptPath('../app/views');
Zend::register('view', $view);
$controller->dispatch();
?>
So, what was wrong about it? Well, it looked pretty much like the code I previously had. I can tell you, that had nothing to do with 0.2.0. The author sure did not try to run the samples. First, Zend_Controller_Front::getInstance() no longer existed. The front controller class was now a simple class that had to be created like any other object. Same for setRewriteBase(), it’s gone.
The other major change in 0.2.0, the path routing is now independent from the controller/action mechanism. In the previous release, a path like http://www.example.com/foo/bar would have called barAction() on the FooController class. Additional parameters could be passed in the URI using /foo/bar/key1/value1/key2/value2/, which is quite ugly. The new routing mechanism allows to define custom paths using a syntax like ‘article/:year/:id’, where elements starting with a colon are variables. Using the previous example, http://www.example.com/article/2006/10 would be a valid URL and send the ‘year’ parameter as 2006 and ‘id’ as 10 to the selected controller/action. So, extra steps are actually required to bring a compatibility layer to the previous version. This is what the code should look like.
<?php
include 'Zend.php';
function __autoload($class)
{
Zend::loadClass($class);
}
$view = new Zend_View;
$view->setScriptPath('../app/views');
Zend::register('view', $view);
$router = new Zend_Controller_RewriteRouter();
$router->addRoute('compat', new Zend_Controller_Router_Route(
':controller/:action',
array( 'controller' => 'index', 'action' => 'index' ) ) );
$front = new Zend_Controller_Front;
$front->setControllerDirectory('../app/controllers');
$front->setRequest( new Zend_Controller_Request_Http(
'http://www.example.com' . $_SERVER['REQUEST_URI'] ) );
$front->setRouter($router);
$front->dispatch();
?>
I had to spend quite a while reading API documentation, unit tests and bug reports to figure out a routing problem. The line with the setRequest call really should not be required. The framework should find out about it, just like it did in the previous version, but without it, there is no way to get anything routed to anything else than the default controller/action.
Other than this problem with the preview 0.2.0 release, the Zend Framework is really promising. The MVC pattern is highly inspired by Ruby on Rails, there is no doubt about it. In fact, the framework documentation is refering to the RoR documentation in the section about routing. There is a whole lot more to the framework than MVC thought, I will get to other components an other day.