Ruby on Rails has become unbelievably popular the last couple of years. It divides applications into three layers; models, views and controllers, or the shorter term MVC. This means that the presentation is separated from the application logic and the data. It seems that everyone wants to make their own Ruby on Rails clone, and similar web frameworks exists now for Java, PHP, Perl and Python, to name some. I have some experience with Ruby on Rails myself, but most ever learned is now forgotten. I am not going to talk about Ruby on Rails, but about two offsprings; Catalyst and Symfony.
I have worked with Catalyst the last two months, and I have become very comfortable with its MVC implementation. Almost everything is based on components, and Catalyst presents convenient methods for accessing controllers, models and views. Plugins can extend the core functionality of Catalyst by providing methods for easy access, for example to localize strings or assemble HTML widgets and forms. Methods imported from plugins are directly accessible from the context object to all components in your system. You can also create your own plugins, for example to access a cache solution.
Symfony is based on many of the same principles as Catalyst, to separate views, controllers and models from each other. Plugins can be used to extend the functionality, but not only for the core. If you want to integrate with a database using an ORM-solution (object relational mapping), as many do when using a web framework, you have to include a plugin for that. You don’t have to, but there are already plugins for this functionality, and you don’t want to reinvent the wheel, do you?
The most obvious difference between Catalyst and Symfony are that they are written in different languages, Perl and PHP respectively. URL mapping in Catalyst is handled in each controller by defining the dispatch type in the method signature. The default is to map URLs to controller names, and because of the namespace support in Perl known as packages, this makes it easier to create hierarchical structures in Catalyst. This means that you can create controllers with deeply nested actions in a clean and logical way. An example of this can be taken from the Bifrost project where the URL “http://example.com/admin/cards/lastswipe/create” is mapped to the package “Controller::Admin::Cards::Lastswipe::Create”.
PHP has no knowledge of namespace, and Symfony does therefore only support two levels in the controller view; modules with one or more actions. It does however map URLs to actions using a configuration file, so it is possible to get multilevel URLs in Symfony too. Because of the mapping configuration it is not always given that the example URL above maps to a class named “AdminCardsLastswipe”, it could just as well be mapped to something like “fooBar”. This gives flexibility to the applications, because URLs can stay the same even if the files are move. This is also achievable in Catalyst as well, but would be an exception of the normal rules, and can be spotted when starting up the application server in debug mode. As long as only you are working on the project this is not a big problem, but it might be harder for others to understand it. You might even get problems yourself if the application grows very large.
When it comes to the model layer, a common practice with Catalyst is to implement a small model class, used to invoke a model class or sub system with configuration parameters, user session attributes or request attributes. To retrieve a model you call $c->model(“MyModel”). This is also true for the view layer where you access the view sub system by calling something like $c->view(“TT”). Normally you won’t have to explicitly call on the view, because it will automatically use the one available, or the one configured as the default view if you use multiple views.
In Symfony, the view and model layers are included as plugins to extend the core. This locks you in to use only one view and one model implementation for each application. You can of course implement new methods to return what ever you want, but then you go outside the scope of Symfony. In fact, an ORM called Propel is already integrated in Symfony, and you need to use a plugin to override this behaviour. From what I can see from configuration files, you can only use model in your application. You can probably configure each module to use different models, but you are still stuck if you want to search in two databases from a single action. This brings me over to the next part; flow control.
Actions in Catalyst are methods in controller modules. You can forward to other actions, and the original flow of execution is resumed when the actions end. This gives you flexibility to gather data from multiple actions without being cut off. If you detach to other actions, the chain will break off and won’t resume to the original action. If an action you forward or detach to throws an exception, the exception is thrown back through the action stack, which. This makes it easier to catch and handle exceptions because the exception will always end up in the first action method.
In Symfony, actions are methods in modules. When forwarding you get the same effect as detach in Catalyst. You can use filters in Symfony to achieve some of the same effect as forwarding in Catalyst. The difference is that you have not reached your action yet when running through your pre-filters, which means you can not handle exceptions in your action class. Filters are often used for caching, authentication and authorization.
Perl has a template framework called Template Toolkit which separates application logic from presentation. What I like about Template Toolkit is that there is no need for any Perl syntax inside the template files, which I believe is important when graphical designers are working on the templates. There are also other template systems for Perl, for example HTML Template.
Symfony uses a template system which depends on PHP to print variables, handle flow control and to call view helpers, but using PHP in the template files can result in unpleasant results if used to handle too much of the flow control. A thumb rule might be to never let PHP code exceed more than one line at a time in the template files. It is possible to use Smarty, a template system with many of the same features as Template Toolkit. It is also possible to use XML and XSLT in the view layer, but traversing XML files can be a tough experience for most people.
Now, lets focus back on MVC frameworks and Ruby on Rails. Of the two web frameworks I have discussed above, Symfony is the one that looks most like Ruby on Rails, but that doesn’t mean it’s perfect. Catalyst has a stronger separation between the layers, which provides more flexibility, and I like that. When working on Bifrost we had to integrate with multiple sources of information, such as LDAP for data storage, ARP for looking up MAC addresses and CUPS for listing printers and printing documents from web forms. This was all done in models. If this had been done in Symfony, we had to implement those features directly into the controller layer or create a lot of plugins.
The world of web frameworks is large, and it is definitely worth learning to use some of them. It didn’t take long before I was able to understand Catalyst and use it effectively. It has taken a little longer with Symfony, probably because I find PHP harder to use, because of inconsistent function and configuration names and the lack of namespaces. Symfony focuses a lot on caching to optimize for speed, and this is of course needed because the entire framework gets loaded on each page request. I find Catalyst more elegant than Symfony, not only because of the programming language, but also because Catalyst focuses more on good software design and that it pre-loads the framework and components once, and then forks to multiple daemon processes to share memory and save resources.