No Ruby Tuesday this week, as I've not been Ruby programming. Instead, I've been having a look at the Symfony PHP framework, to see whether it shapes up to Rails. As I've stated previously, Rails is a pain to run on dirt-cheap hosting (though options like Planet Argon are out there, cheap enough, but I haven't tried them yet); plus Ruby is another language to learn for the people I work with at OpenAdvantage, and most of them have got enough to do running small businesses. I wondered whether Symfony is good enough to recommend as an alternative to Rails for programmers who already know PHP.
In short: it is.
It's not perfect, not as concise as Rails, and lacks many of the features. But using it is a damned sight easier than writing a PHP application from scratch. I'm not going to do a thorough feature listing (see the Symfony site for that). Instead, here are some of the notable things about it that improve the life of PHP programmers:
Next, a few specifics and code samples to give an idea of how it works.
I chose a wiki as the project to experiment with, and have spent about a day and a half coding in Symfony. The result (fancifully called Flow My Thoughts, The Policeman Said) is an extremely simple wiki which supports camel-case links, [[]] links and Textile markup.
For the wiki parsing part of it, I borrowed some code from the Drupal freelinking module and from TextilePHP. Along the way I fixed TextilePHP so that it works properly with strict error reporting on (there were lots of references to unset variables and possibly non-existent array indexes which meant I got around 100 error messages the first time I ran it). This means I can write wiki links as well as mix in Textile markup (I am a big fan of Textile). Don't you just love open source?
For the main part of the project I started with the Symfony sandbox download, as this includes all the libraries for Symfony in a tarball. You can also install the dependencies with PEAR, and would want to if using Symfony for several projects. I also went with SQLite for experimenting with.
First off you edit a schema.yml file with details of your data model. This works pretty well, and is a bit like writing a migration. You can either use the sensible Symfony defaults or work with the Propel syntax directly (e.g. if you want specific options or indexes set for fields). My model looks like this:
propel:
page:
_attributes: { phpName: Page }
id:
title: { type: varchar, size: 255, index: unique }
content: longvarchar
created_at:
updated_at:
Nothing too unusual there. Also notice the "magic" Rails-style fields, created_at and updated_at.
To build the model classes from your schema, you run:
php symfony propel-build-all
The symfony file is a Pake build script. The propel-build-all task generates some XML from your YAML files, which gets converted into PHP classes representing your tables. In my case, I got four files: BasePage, BasePagePeer, Page, PagePeer. Then it generates some SQL and builds your database. All very migration-like. However, there's no sense of "up" and "down" in Symfony, and it's probably hard to version the database schema incrementally, like you can in migrations.
As I mentioned above, the PHP classes are chock-full of getter and setter methods. Far more wordy than ActiveRecord's default two-line class definitions, and a consequence of the lack of runtime class extension in PHP. However, only the Base classes (in my case, BasePage and BasePagePeer) are regenerated when you recreate the model using Pake. This means you can add your extensions into the classes which inherit from the Base classes (i.e. Page and PagePeer).
Symfony supports generation of a CRUD interface (i.e. scaffolding) from the model too:
php symfony propel-generate-crud frontend page Page
This actually creates an application called "frontend" inside your app. directory. This is interesting, as it illustrates how Symfony applications can be structured into parent-child relationships, unlike Rails where it's pretty flat.
You can get at your application in development mode by browsing to (e.g.) http://localhost/frontend_dev.php/page. This will use the default views for the CRUD interface.
The scaffolding sits inside the frontend application, and can be used to selectively override the defaults for the application. I added some directories to my page module:
apps
frontend
modules
page
actions
lib
helper
templates
validate
Note here that I can either add helpers for the module, or I can place them under frontend > lib > helper to add them to the whole application. This mirrors the distinction between application level and controller specific helpers in Rails, but is reflected by the directory structure, rather than by file naming. This seems unnecessarily complex, particularly if you're used to Rails. The other directories are fairly obvious, hopefully.
Routing is one area which had me foxed. I wanted to be able to use a URL like this:
http://localhost/wiki/page/NameOfMyPage
to be able to navigate to the page with the title NameOfMyPage. However, I couldn't for the life of me get this to work. In the end I had to settle for:
http://localhost/wiki/page/show/title/NameOfMyPage
Note that the link_to helpers are a bit unwieldy compared to Rails, particularly as you don't have the luxury of symbols and automatic hashes from arguments like you do in Ruby:
echo link_to($page_title, 'page/show?title='.$page->getTitle());
Even though this is only slightly more opaque and verbose than Rails, I think it would wear on me after a while.
The scaffold specifies the actions you'd expect by default: for the page model, they ended up in apps/frontend/modules/page/actions/actions.class.php. Adding my own was simple enough. For example, I wanted to show a page by title. Showing a page by ID was supported by the scaffold already:
public function executeShow() {
$this->page = PagePeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->page);
}
Mmmm, not quite as succinct as Rails, I was thinking. You can see the Torque-style (Propel) syntax for retrieving records in the first line of this method definition. Note that the template to render is implicit, and maps to apps/frontend/modules/page/templates/showSuccess.php.
First, I added a new method to the PagePeer, to retrieve a record by title. This is located in lib/models/PagePeer.php, outside the application. The idea is you are likely to have multiple applications supporting a single model. On the other hand, it's probably possible to create models specific to individual applications (maybe even modules):
public static function retrieveByTitle($title) {
$c = new Criteria();
$c->add(self::TITLE, $title);
return self::doSelectOne($c);
}
The Criteria class used here is part of Propel, and it works pretty well (again, very Torque-like). Though again, more verbose than ActiveRecord. (Thanks to Scott Meves' comment which cleaned up the above function.)
Then I added an action to frontend/modules/page/actions/actions.class.php:
public function executeShowByTitle() {
$this->page = PagePeer::retrieveByTitle($this->getRequestParameter('title'));
$this->forward404Unless($this->page);
$this->setTemplate('show');
}
Notice that this reuses the 'show' template (setTemplate() specifies the one to use if different from the inferred template).
I created my own helper to show error messages in apps/frontend/lib/helper/ErrorMessagesHelper.php:
<?php
function error_on($field) {
$error = sfContext::getInstance()->getRequest()->getError($field);
$out = '';
if ($error) {
$out = content_tag('div', $error, array('class' => 'error'));
}
return $out;
}
?>
Notice how I had to go round the houses to get at the error messages on the object. For some reason, error messages aren't stored on the object, but in the request. This makes them pretty hard to get at, and you have to use the Symfony context if you are not explicitly within a view. Also notice that there is a content_tag helper.
I can load this my helper into a template with:
<?php use_helper('ErrorMessages') ?>
Nothing special here. Just HTML with PHP, plus some PHP helpers to make life a bit easier. For example, here's my edit form for pages:
<?php use_helper('Object') ?>
<?php use_helper('ErrorMessages') ?>
<?php echo form_tag('page/update') ?>
<?php echo input_hidden_tag('id', $page->getId()) ?>
<p><strong>Title</strong>
<?php echo object_input_tag($page, 'getTitle', array('size' => 80)) ?>
<?php echo error_on('title') ?>
</p>
<p><strong>Content:</strong><br/>
<?php echo object_textarea_tag($page, 'getContent', array('size' => '80x10')) ?>
<?php echo error_on('content') ?>
</p>
<hr />
<?php echo submit_tag('save') ?>
<?php if ($page->getId()): ?>
<?php echo link_to('delete', 'page/delete?id='.$page->getId(), 'post=true&confirm=Are you sure?') ?>
<?php echo link_to('cancel', 'page/show?id='.$page->getId()) ?>
<?php else: ?>
<?php echo link_to('cancel', 'page/index') ?>
<?php endif; ?>
</form>
I could have used the short PHP <?= ?> syntax to make this less verbose, but went with the most portable syntax. Note that there are equivalents for Rails' form_remote_tag etc..
Validation is set up in YAML files (reminds me of Struts). For example, I wanted to make sure that both title and content are set for a wiki page. To validate an action, you have to add a YAML file named after the action: in may case, I wanted to validate the update action, so I added apps/frontend/modules/page/validate/update.yml:
methods:
post: [title, content]
fillin:
enabled: on
names:
title:
required: yes
required_msg: 'Please enter a title for the page'
content:
required: yes
required_msg: 'Please enter some content for the page'
Again, not as nice as Rails, but more declarative, perhaps. The fillin option has to be enabled for auto-form-population to be turned on.
I haven't mentioned the testing framework which is built into Symfony, as I didn't use. Note also that it supports separate development and production environments, like Rails, but they run concurrently: you access the development environment through a modified URL, rather than by running the application in a different mode. That's quite nice.
Another feature I liked is the console you get in development. This shows you log messages pertinent to the page, the SQL queries run, and general stuff about the environment, presented as an AJAX-ified menu across the top of the app.. I'd love to see something like this in Rails.
I started putting Ajax into the application, but in-place editors don't work very well with wiki pages (you need to be able to click on links in the page when a page is displayed, but the in-place editor turns the whole page into a clickable field which displays an edit form, as in Rails). So I abandoned that. I also experimented with using the TITLE field as the primary key instead of ID (use of ID fields is encouraged in Symfony, as in Rails), but this was hard work, so I went back to IDs. I also didn't get round to authentication.
That's as far as I went with the application, and I'm unlikely to go further. I've attached it to this post for anyone who's interested (licence is GPL).
I put these few samples of Symfony code up to give a flavour of the differences and an impression of how it feels for a Rails programmer. It is not a thorough or complete evaluation. Try it for yourself. I have to say that the tutorial on the Symfony website is pretty good; and you can also get a full book on Symfony from the site (or a print version). (Come on DHH and Dave Thomas, when are we likely to see Agile Web Development for Rails given away? I've already bought it twice.)
Symfony has a lot going for it. It's pretty easy to get up and running. But by contrast with Rails, it is very verbose because of how PHP works. On the plus side, it is similar enough to Rails to make it fairly easy to transition to; and you can run it on normal web space at high speed without having to fork out for special hosting. That last point is very important to my mind. Coupled with the fact that there are lots of PHP programmers out there already, or Java programmers looking for a lightweight framework but scared of Ruby, and I can see it growing at a reasonable pace. I would certainly consider it if I needed to write a scalable web application for myself; I've even considered rewriting Flickrlilli using it, to see if it makes a difference and to get a better comparison. You never know...
| Attachment | Size |
|---|---|
| fmt_wiki.tar.gz | 1.58 MB |
Comments
What CMS's run well on Symfony?
We're strongly considering moving to Symfony from our own CMS system which we built using PHP with Smarty templating. We need to find the best, most flexible, most powerful CMS available if we go in this direction. Can you recommend any particular CMS for use within Symfony?
Hi Ben. I'm not aware of any
Hi Ben. I'm not aware of any CMSs written with Symfony. I'd go for Drupal under any circumstances :)
About the Symfony framework
Hey, glad to see the symfony web sphere is getting bigger and bigger. As a symfony user/developper since two years now, i'm happy to see how it's used by more and more people. But maybe you could point out that Propel is not the only O.R.M available for symfony, an under-development ORM called PHP-Doctrine is increasing stability every day, and as the #doctrine channels topic clams, "RC 1 to be released at 31st of August". Doctrine is far mor contraining than Propel, as it does not generates static methods but insteads allows you to make complex arbitrary joins in your schema following the DQL (Doctrine Query Language). I worked for one year with propel, and headaches came so fast i had to find a solution. Even is doctrine is not yet 100% ready for production use, it will be soon, and i personnaly use it in production on some small sites already.
Keep spreading the word :-)
Thanks for letting me know
Thanks for letting me know about that. I too have had issues with Propel, as it seems pretty clumsy and inflexible in places. I will mention Doctrine in the article and have a look myself.
Propel queries
This was a great article! In case anyone was following along, I believe you could reduce your retrieval function even more and use the "doSelectOne" method available in your base peer classes:
Nice one! Glad you found it
Nice one! Glad you found it interesting.
Fantastic, thnx...!
Thank you very much for "getting your hands dirty" and sharing your expiriences with symfony. Such a review from someone who has actually coded something is much more valuable than all those comparisons that only show information gathered from different websites.
Cheers, glad it was useful
Cheers, glad it was useful and thanks for taking the time to comment.
Thanks for the Great Post!
Great post. I was looking for a rake-like helper for PHP (Pake, as you mention) and decided to keep reading to hear what other tools Symphony offers. I initially considered using Symphony to build my latest web app but decided against it for performance reasons. I think it's most sensible (for both pragmatic and performant development) to use a "best-of-breed" composite framework, taking the best (hopefully decoupled) bits and pieces from existing solutions and using those where applicable, thus avoiding the standard framework bloat. Thanks again for the writeup!
Thanks for commenting. I
Thanks for commenting. I tend to agree: symfony is a bit big and complex, and I had a hard time getting my head round some of it (the Apress book is OK but not particularly task-oriented: it explains where everything is and what it's for, but not how to use it). I've been working with Propel and Smarty this weekend, and have managed to put together a fairly lean and simple MVC framework in about a day, with some quite Rails-like features but without the complexity of symfony. I might have a go at building something with that, rather than symfony.
2 cents.
Nice review. I've been using Rails for several months, but as a new project had PHP as a requirement I adopted symfony for that. While it is a decent framework indeed, I still found it to be rather painful compared to Rails (and Ruby, that is). Several features are lacking, but more importantly the documentation is rather poor and, at times, not helpful at all. The community support is nowhere as good either. It was a good experience nevertheless, but I am very much looking forward to going back to Rails...
Thanks Adrian. I think my
Thanks Adrian. I think my experience was similar to yours: nice, but more painful than Rails. Though I did wonder whether this was because I know Rails pretty well, and Symfony not at all, so maybe my existing experience was colouring my perceptions. I found the documentation OK as far as tutorials went, but the API documentation a bit lacking in explanation.
Routing
Very nice article. I often read people doing pure speed comparisons of frameworks and concluding that framework x is rubbish because it's 10 requests/second slower than y. It's good to see someone getting their hands dirty and testing properly!
The routing example you gave would be something like:
Thanks Michael, that's a
Thanks Michael, that's a nice compliment. I'll definitely be looking at Symfony again for my next PHP project. Thanks for the routing tip too.
Symfony Rocks
I've been using symfony for about 6 months and I can't yet calculate how much time I've saved hacking it. I rebuilt a website that took 3 months to build from scratch in 3 weeks. I am still using 0.6.3. It looks like you were using 1.0 Beta. As of today, 1.0.0 is stable.
The guys that developed it are pretty amazing. Their community is very strong due to the great documentation they provide. I have had a lot of problems with rails running on Shared Hosting. Ruby seems to have the same problem as Java. It seems to be too expensive/complicated for small business to run with.
Thanks for your insights:
Thanks for your insights: always useful to hear about people using this stuff successfully. I am toying with writing my next project with Symfony and giving it a fair go; Rails is turning into such a hosting pain that it actually puts a thorn in the side of my attempts to use it. You're also right about the documentation: while not perfect, it is pretty good, and enough to get you started. 1.0.0 was pretty stable, as far as I could tell. My only real issue was the morass of config. files, and working out where to change stuff: one area where the documentation is a bit sketchy. But I've been impressed with it, and will definitely have a better look the next time I need a web application. Cheers.
Akelos seems to be closer to Rails
Hey Elliot, really interesting article. I'm about to pick a framework for my company's next project and really like playing with Rails, but when it comes to my pals.... They only know PHP and do not show interest in learning a new language - (This is note is for Dave and Thomas and Andy Hunt ) I wish there were Spanish translations The Pragmatic Programmers books so they could change their minds :P
The case is that someone has tried all the Railish PHP Frameworks (Symphony is not railish enough for being in the list) and it seems that Akelos behaves more like Rails (philosophy, simplicity and flexibility) than any other PHP framework out there.
I'm gonna give a try to Trax and Akelos.
Any bad/good experience to share about any of them?
Thanks a lot for the
Thanks a lot for the comment, Sergio, and for the compliment. I've come across Trax briefly, but not Akelos. I'll investigate both, I think. It is important that I can tell my PHP friends about alternatives, as they often haven't got the time or inclination to learn new languages, or have already invested a lot of time in PHP and like it, or need simple, cheap solutions which can be easily hosted (which Rails isn't).
CakePHP is pretty decent
Nice review. As a Rails programmer, I, too, sometimes have to work with PHP, and I long for the perfect Rails equivalent. Of course, without Ruby, you're limited by the language, but I recently did a project with CakePHP and found it a fairly close equivalent to Rails.
Thanks, Curtis. I've used
Thanks, Curtis. I've used Cake a bit in the past (well, about a year ago, to be precise). At the time, it felt a bit clunky next to Rails, so I dismissed it. Given my issues with Rails recently, I might give it another spin as it's probably much better by now.