tech

Drupal Association

Excellent news - there is now a formalised non-profit association to support Drupal. From the full press release:

The Drupal Association exists to provide the logistical and financial foundation necessary to support the Drupal project's exponential growth and to provide the Drupal project with new possibilities regarding infrastructure, marketing and funding. The Drupal Association will not be involved in decisions regarding the development or direction of the Drupal project itself.

This will make it possible for people to donate directly to Drupal (rather than individual developers), help create infrastructure to support the growth of Drupal, organise events and actively promote Drupal. Can't be a bad thing.

It's all gone quiet over there

I haven't been blogging much recently. I kind of fell out of love with it for a while, and realised I was putting myself under too much pressure to produce stuff. While I have a few readers, I decided it's not worth the effort forcing myself to write stuff, just to keep my blog popular. So I've been kicking back, going to bed early, leaving the computer at work, doing nothing in the evening, watching more DVDs. Plus I was off work for a couple of days with a cold, which took me out of it for a while. That's why it's been a bit quiet round here.

But I'm back in the saddle now, rewriting my PHP course and reviewing open source CRMs for the OpenAdvantage website (SugarCRM has been occupying me for most of the last week: it's a beast [how bloody hard is it to setup email campaigns?!], but I figured it was worth indulging the time, as people keep ringing me up about CRMs - it's this year's CMS). I spent a couple of days just writing a glossary of CRM terms to get my head around it (which will be part of my review eventually). Any suggestions for open source CRMs I should consider for my review, put 'em in the comments. Here's my current short list (NB some of these aren't CRMs, or just have CRM bits, but I thought alternatives for contact and project management might be handy too):

  • SugarCRM (the daddy)
  • activeCollab
  • EGS
  • Centric
  • Queplix
  • CiviCRM
  • CentraView
  • hipergate
  • vtiger (is this anything other than a SugarCRM clone?)
  • Daffodil
  • OpenCRX
  • Cream
  • Ofbproject
  • Compiere (vast, looks nasty)
  • Ohioedge
  • OpenERP
  • OpenTAPS
  • xanders:one

Some of these aren't even open source (probably) and some will get dismissed flippantly out of hand (I haven't got time to go in-depth on all of them), but there you go.

Next week sees me visiting the House of Commons for the launch of the National Open Centre project (yes, it's a Drupal site). Funding is still being sorted out, but we're hopeful it will come off. The launch "party" will be the official start of the project (without funding, but we're going ahead anyway). I'm still working full time at OpenAdvantage, and it will be interesting to see how (if) we morph into the NOC, and what my duties will be then. Like OpenAdvantage, it's tricky to predict until you get into it, but I'm sure it will be interesting: open source and open standards, but at a policy level, is the aim, but how that will translate into day-to-day work I'm not sure.

Open source CMS seminar at OpenAdvantage

I don't normally advertise OpenAdvantage events on this website, but I'm really proud of the one we've organised for the 28th February on Open Source Content Management. We've put together four speakers from UK small businesses (three from the West Midlands), each talking about a prominent open source CMS:

The idea of the seminars is to give a warts and all account of how open source CMS can be used within a business, not from a sales angle but from a technical/business one. The seminar is FREE to anyone based in the West Midlands region. I think it's going to be a really interesting morning, and I'd encourage you to sign up now.

Yahoo! Pipes - a programmer's perspective

Everyone seems to be rabbiting on about Yahoo! Pipes. It's an impressive Web 2.0 Ajax GUI for building "mashups". You basically get a series of components you can plug together to build a feed-producing web application. These can be grouped in terms of functionality roughly like this:

  • Sources: You can either choose an RSS feed (I wanted to fetch the HTML of a web page, but couldn't seem to do this), or one of the predefined searches (Flickr, Google Base, Yahoo!).
  • User inputs: Get users to input something, or supply a default. This is a bit of a misnomer, as they are really generic inputs. Getting user input is optional, as you can just supply a parameter in the URL when calling the pipe if you prefer.
  • Operators: These enable you to perform operations (unsurprisingly) on outputs from other components (e.g. Sources). These include:
    • Filtering (include/exclude items based on the content of XML elements; for example, if you're filtering an RSS feed, you can filter by any of the elements inside an <item>; by the way, this seems to be case sensitive, which is a bit of a pain).
    • Looping (apply a set of operations to each item, either adding to or replacing that item with the output from that set of operations - the example they use is stuffing photos from Flickr into each feed item)
    • Limiting (restrict the number of items returned, remove duplicates)
    • Counting and sorting
    • Translating (using Babelfish)
    • Merging (put two or more feeds together)
    • Tagging (with location data or detected keywords)
  • Formatters: Format dates, build URLs from parameters, concatenate strings.

It's quite a limited tool set (for example, the transformations possible are quite limited - you can't generate HTML or XML other than RSS-style feeds, do any advanced path-based querying, or seemingly use any old HTML page as an input). But it is very powerful if you're a non-programmer who doesn't have access to this stuff through raw coding.

As to the interface: apart from a few display bugs on Firefox, it works remarkably well, and has some extraordinary touches (like the rendering of the pipes themselves) which make it feel very desktop-like. Here's a screenshot:

As a coder, I abhor GUIs for writing applications, and actually find them conceptually difficult to approach (I'm not a visual person). It took me a while to work out how to actually run a "pipe" (the name for an application you've built) (double-click on the Pipe Output component and it displays in the debug window) or run them from a plain URL (click on "Back To My Pipes" - "kicking off my boots/going back to my pipes" - to see the list, click on a name, then click on "Run"). Plus the interface got a bit cluttered, and some things didn't quite seem to work (for example, if you're using a filter, it will allow you to filter on the XML elements in the output document - so you need to provide both filter conditions for the elements of RSS feed items and for the elements of Atom feed items if your filter is supposed to work on both).

Here's the public URL for the pipe above (which shows you items from my RSS feed matching some word): http://pipes.yahoo.com/pipes/Lqqx2yy42xGybXUdJxOy0Q. Or you can call it with a querystring to supply the search term: items with Drupal in title or body. Or get the results as RSS. (One gripe here: I'd like to just take the original URLs and append &_render=rss, but this doesn't give you what you want. As it is, I just got the RSS URLs from the main pipe display.) This is actually useful already: it means anyone can just get items from my RSS feed matching some keyword they are interested in.

You can go further and make this more generic, so the user could request any feed and keyword, and the pipe could return matching items: like this pipe I cloned from the above. For example, this link gets entries matching the term "Jokosher" from Jono's feed; this one gets items matching the term "ingredients" from Chloe's feed. In both cases, you can also get an RSS feed for the pipe: here's one for Jono and Jokosher, another for Chloe and ingredients. This makes Pipes even more useful, and turns it into something I could even see myself using: very useful for writing quick filters and aggregators of RSS feeds where your needs are simple. For serious web scraping it's not quite the ticket, but people like me are not necessarily the target market anyway.

It reminds me of trying to write music using PureData: you're plugging outputs into outputs, putting boxes to filter outputs before they reach inputs, etc.. Kind of like a web patch panel for you old-time synth enthusiasts.

To conclude: really nice, great for building simple feed consumers and manglers, and definitely worth a look if you're not a programmer but want to get yourself a piece of Web 2.0.

Upgrading from Drupal 4 to 5

Since I've upgraded townx.org, I also had to upgrade mooch labs and Rails West Midlands. I've finished this now.

By the way, upgrading from Drupal 4 to Drupal 5 wasn't as straightforward as I'd like. The database upgrades worked for my two simple sites, but I rebuilt the database from scratch for this site, as things didn't quite seem to work when I tried to run update.php. Not sure why. So here was my process:

  • Set up staging.townx.org (domains, new web space etc.).
  • Put an entry in /etc/hosts to point to staging.townx.org (so I don't have to wait for DNS propagation).
  • Copied Drupal 5 install to the staging.townx.org directory.
  • Copied existing database to townxstaging database.
  • Set up connections in sites/staging.townx.org/settings.php to point at townxstaging database.
  • Ran update.php on staging.townx.org (NB you have to set $access_check = TRUE in that file for the script to run). A few things didn't work, possibly because I had a non-standard theme, so a lot of my theme settings didn't work and the page went blank. Tip: If you're upgrading an existing site, set the theme to Bluemarine before you start upgrading. But my database structure did get altered properly.
  • Created another clean database, townxstaging2, pointed settings.php at that, then ran the Drupal install script to create the database structure. staging.townx.org now contained a blank, default Drupal install.
  • Manually installed the modules I need by downloading the versions for Drupal 5 from http://drupal.org. These are the modules I use:
    • Textile: there's not a release for Drupal 5, but CVS HEAD version with a patch works fine.
    • Pathauto: available for Drupal 5.
    • Captcha: available for Drupal 5.
    • CommentRSS: available for Drupal 5. I added my own modification to put links onto nodes.
  • Manually copied over data from a whole slew of tables from townxstaging to townxstaging2 (note that as I'd updated townxstaging, the tables were in the right format for import; this won't work if you're trying to directly import from a Drupal 4 to a Drupal 5 database):
    • blocks: I recreated all the blocks from scratch.
    • boxes: Again, I recreated blocks from scratch, cutting and pasting the text for the boxes I need from my old Drupal to my new one (I really should turn them into one or more modules, probably). Text Link Ads only points you at the Drupal module now, which isn't available for Drupal 5 yet. So I copied over my old box code and modified it a bit to fit the Garland theme.
    • comments: these imported fine.
    • files, file_revisions: These worked fine. I also needed to copy my files directory from my old install into the new one.
    • filters, filter_formats: I had to reconfigure all of my input formats. However, as my nodes still referenced the old IDs for the filter formats, I had to manually fix the IDs in the filter_formats table.
    • menu: This transferred over fine.
    • node, node_comment_statistics, node_revisions: This caused the most hassle, as the ordering of fields in the table has changed between versions. Tip: If you are dumping data out of node_revisions using phpmyadmin, make sure you dump using complete inserts, as this includes the field names in the INSERT statements. Note that you also need to set something in the log field for each record: NULL won't do. So during the export I set the log field to '' for all nodes where the log field wasn't set.
    • sequences: I imported these OK. Tip: Make sure any settings in your new database are overwritten by settings from your old database. Otherwise you get weird insert errors, as Drupal will try to reuse IDs that already exist.
    • system: In the end, I recreated this from scratch, and didn't use any of my old settings. This meant quite a bit of hassle, but it did give my system settings a much-needed spring clean.
    • term_*, vocabulary_*: I just imported all of these.
    • url_alias: Imported fine.
  • Note that I ignored watchdog, session, cache_* etc. (ephemeral stuff). I also ignored variables, and just recreated that through the GUI. Some things I don't use (like contact, forum), so I ignored them. I ignored history too, which you might not be able to do on a site with multiple users.
  • Manually copied my modifications to the page template into the Garland theme. These include some <meta> tags, and some Javascript to run my Blogbeat monitoring.
  • Copy my modified .htaccess file (to run under PHP 5 and redirect some domain names onto others).
  • Once I had staging.townx.org running properly, I moved my old site out the way, moved staging into its place, renamed a few databases, and hey presto, it seems to be working fine.

There were other bits and pieces I've probably missed, but that about sums it up.

Switch to Drupal 5

I finally got round to swapping to Drupal 5. This has taken me a good three hours work, fiddling with databases, exporting SQL, moving files around, checking stuff out of CVS etc.. The upside is I now have a nice cleaned-up install. There are a few minor differences, but not too many:

  • I've had to dispense with my Atom feed, because the module seems moribund. Hopefully there aren't many people using this.
  • I'm using the default Drupal 5 theme, rather than Friendselectric. Hope you like it.

Let me know if you come across any issues.

Maybe I'm getting older... (a short rant on accessibility)

My eyes aren't what they used to be. (I remember when all this was fields...) Consequently, I have the fonts pushed up to a minimum size of 16 inside Firefox, to stop me getting eyestrain. I also have the Google Toolbar and the Web Developer Toolbar switched on all the time. All of which means the amount of space available for web pages is pretty minimal. (My display is 1280×768, as I just use my laptop all day rather than an external monitor: providing I keep the fonts big and the backlight up, it's fine, particularly as the IBM laptops have such great keyboards and I use an external mouse).

All of which preamble leads up to this point: why are there so many websites which break when you turn the font up or have reduced screen real estate? A few examples from today:

  • ZDNet: the menu is all over the place (usable, but very ugly).
  • FAST: crappy Javascript menus disappear under my status bar, so I can't select the options at the bottom.
  • scRUBY!: text all over the place at the top of the page, rendering it almost illegible.
  • gubb.net: unusable for me, as the menus disappear off the bottom of the screen (plus you can't set recurring reminders).
  • Jokosher (sorry, Jono): one of the menu items (Contribute) floats around virtually invisibly over the main part of the page.

I could go on. Please, please stop trying to make everything look like a printed brochure. Just build websites that people can use with their own choice of font and browser window size (fixed-width designs with absolute positioning which don't scale properly are the main culprit). I feel like I've been banging on about this for years, and still nothing changes. Accessibility is not just about people with disabilities: it's also about catering for old timers with fading eyesight (and greying hair). Not everyone uses 8 point fonts on an enormous outdoor-movie-theatre-sized monitor.

(By the way, I know my Drupal theme sometimes screws up, so I'm not innocent. I'm planning an upgrade to Drupal 5 before too long.)

Jokosher testing on Ubuntu Dapper

Jokosher is an audio production tool, designed for multi-track recording, which runs under Linux. The primary design goal was ease of use. Jono today called for Jokosher testers, to iron out bugs in Jokosher for version 0.9.

The application has come a long way, and is kind of usable in its current incarnation (not currently suitable for the kind of music I do [electronica], which needs lots of low-level wave editing, sampling and looping, but I'm just putting in bug reports and feature requests). It is impressive what's been achieved so far, and I'd like to see it become more general-purpose, so I can use it for my own audio production. I had got 0.2 working pretty easily, and submitted a few bug reports, then realised the team wouldn't be interested in that, and had probably fixed a load of the bugs. So I decided to bite the bullet and try it out properly from the latest version.

The biggest struggle is getting the thing installed. You're OK if you have a bleeding edge Ubuntu (Feisty) as it is packaged for that, but if you want to help with the testing, you'll need to get the CVS versions of Gstreamer and associated libraries, plus a checkout of Jokosher from Subversion. This turns out to be a bit of a pain, so here are some pointers if you're on an old Ubuntu (like me with Dapper). I won't replicate all the documentation here (there is a fine amount on the Jokosher userdocs website), but I'll try to summarise and provide pointers to the right places:

I got my instructions from http://userdocs.jokosher.org/InstallingCvsGstreamer and http://userdocs.jokosher.org/Installation. But here they are boiled down to the essentials (for Ubuntu Dapper):

# make a directory to put everything into
# (Gstreamer CVS, jokosher etc.)
mkdir ~/apps/jokosher
cd ~/apps/jokosher

# pull gstreamer (and associates) from CVS
mkdir gstreamer
mkdir gstreamer/head
cd gstreamer/head
cvs -d:pserver:anoncvs@anoncvs.freedesktop.org:/cvs/gstreamer co gstreamer \
gst-plugins-base gst-plugins-good gst-plugins-ugly gst-python gnonlin

# pull trunk jokosher from SVN
cd ~/apps/jokosher
svn checkout http://svn.jokosher.python-hosting.com/JonoEdit/trunk jokosher-trunk

# setup dependencies to build everything; NB there are a LOT of these: 
# this is just a list of the ones which have been identified, but there's a 
# chance there are others; you'll soon find out if the compilation step fails...
sudo apt-get install build-essential automake1.7 libtool libglib2.0-dev \
libxml2 liboil0.3-dev python-dev python-gtk2-dev bison flex libxml2-dev \
libgnomevfs2-dev libasound2-dev libspeex-dev libflac-dev libtag1-dev \
libhal-dev libogg-dev libvorbis-dev libid3tag0-dev libmad0-dev

# here's the tricky bit: getting a backport of liboil0.3.8, which 
# gst-plugins-base needs (Ubuntu Dapper has version 0.3.6 or something);
# note that I've done this rather recklessly, and hope it won't cause 
# horrendous clashes with other installed versions of liboil; 
# it doesn't complain, anyway
wget http://backports.org/debian/pool/main/libo/liboil/liboil0.3_0.3.8-0bpo1_...
wget http://backports.org/debian/pool/main/libo/liboil/liboil0.3-dev_0.3.8-0b...
sudo dpkg --install liboil0.3_0.3.8-0bpo1_i386.deb
sudo dpkg --install liboil0.3-dev_0.3.8-0bpo1_i386.deb

# setup a script which will run the CVS gstreamer alongside 
# any existing gstreamer you've installed
cp gstreamer/head/gstreamer/docs/faq/gst-uninstalled ./gst-head

# make it executable
chmod +x gst-head

# edit it: you need to change the line which starts MYGST so 
# it points to the directory ABOVE the head directory, where 
# you checked Gstreamer out; in my case:
#
# MYGST=$HOME/apps/jokosher/gstreamer

# create a script in ~/apps/jokosher/gstreamer/head called 
# build_all.sh to build all your gstreamer bobbins 
# from source, keeping it inside the ~/apps/jokosher directory; 
# it looks like this:

/* START SCRIPT */

#!/bin/bash
# build all the gstreamer stuff
for f in gstreamer liboil gst-plugins-base gst-plugins-good 
gst-plugins-ugly gst-python gnonlin; do
  cd $f
  echo "Building $f"
  make distclean
  rm -rf po
  cvs update -PAd
  ./autogen.sh
  make
  # don't do make install here, as this will trash existing libraries
  cd ..
done

/* END SCRIPT */

# make it executable
chmod +x ~/apps/jokosher/gstreamer/head/build_all.sh

# now we're ready to build
cd ~/apps/jokosher

# setup the build environment so everything points 
# at the right versions 
# of gstreamer etc.; 
# NB calling this drops you into ~/apps/jokosher/gstreamer/head
./gst-head

# and once there you can build everything with:
./build_all.sh

That does the building part. To run Jokosher once you've done all that:

cd ~/apps/jokosher
./gst-head
 ~/apps/jokosher/jokosher-trunk/Jokosher/Jokosher

This worked for me! I'm now ready to try out the delights of bleeding-edge Jokosher.

BBC Consultation Document on On-Demand Services

The BBC are seeking feedback from the public about how their proposed on-demand services should be structured and regulated. There's a document about it on their website which outlines the original proposals, plus how they were toned down by Ofcom to make them more constrained and less useful to licence-fee paying households. I understand people getting jitters about competing unfairly in the marketplace (e.g. entertainment companies which produce CDs and DVDs losing custom because ITV provides downloads). But in the case of the BBC, I'VE ALREADY PAID FOR THE CONTENT (yes, I feel the need to shout). I pay my licence fee, like most other people in the country, and have already funded production of the BBC's programmes. Why should I pay again for a DVD? Why can't I download dramas etc. which I've missed, for free, forever?

It's really important if you live in the UK that you participate in the BBC's Consultation on On-Demand Services. and make your case. I basically went on about the above in my response, plus highlighted that I didn't want DRM, I wanted everything available forever for free, and I wanted it on my Linux laptop. I tried not to sound like a free software maniac and make reasoned arguments with examples. We need as many people as possible to participate in this, otherwise we'll end up with another crippled, inaccessible on-demand service (4od, anyone?). When are the media going to wake up and give people what they want?

I Hear a Symfony: Rails vs. the Symfony PHP framework

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:

  • It bundles in the Propel Object Relational Mapping layer for PHP. I'd heard of this but not used it before. It feels similar to "Torque" for Java, and isn't as neat as ActiveRecord, mostly because PHP lacks some of the advanced meta-programming available in Ruby. This means you end up with getters and setters when you create classes which "peer" for your tables (not neat methods as in ActiveRecord). You also get four classes per table: a Base class, a BasePeer class, a class representing the table (inherits from Base) which you can append methods to, and a peer for the table (inherits from BasePeer) which you can also append methods to. Much more verbose than ActiveRecord. (Note: hartym (see comments) mentions that Doctrine is also available as an alternative ORM layer for use with symfony. Might be worth a peep.)
  • It includes a build tool called Pake, which works in a similar way to Rake but for PHP. This means there are scaffold and migration style tasks you can run.
  • There are separate frontend and backend scaffolding tools. The backend stuff reminds me of Django or Streamlined.
  • It has a built-in (simple) authentication system. However, the fact that someone has thought about authentication probably means it's easier to work into an application than it is in Rails.
  • Internationalisation and localisation are supported out of the box.
  • It has more of a sense of structuring applications than Rails does, perhaps closer to Django. This could mean I could more easily move chunks of functionality around between Symfony applications without having the awkward feeling Rails plugins give me.
  • There are far more configuration files (YAML). It feels more like Struts to me than Rails: you can tweak settings at various levels of granularity (whole of Symfony, per application, or per module). This is good in one sense (for example, validation is carried out in config. files, rather than in model classes, which makes it easier to share validation code around or tailor it for different actions). On the other hand, you do get a splurge of files to manage.
  • It's PHP, so it will run on bog-standard web hosting, backed by any Propel-capable database (including MySQL).
  • By default, it uses SQLite for the database in the development environment. Configuration works in a similar way to ActiveRecord's and is straightforward.
  • AJAX support is built-in (using Prototype and Scriptaculous), and it has the same concept of helpers as Rails. This means you can build interfaces in a similar way to Rails interfaces, and many of the methods have similar names.
  • It is based around a model-view-controller architecture, with a front-controller, very similar to Rails.

Next, a few specifics and code samples to give an idea of how it works.

My specific experiences

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.

The model

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).

Scaffolding

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

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.

Actions

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).

Helpers

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') ?>

Templates

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()): ?>
  &nbsp;<?php echo link_to('delete', 'page/delete?id='.$page->getId(), 'post=true&confirm=Are you sure?') ?>
  &nbsp;<?php echo link_to('cancel', 'page/show?id='.$page->getId()) ?>
<?php else: ?>
  &nbsp;<?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

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.

Other comments

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).

Summary

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...

Syndicate content