03.16.09

Symfony's Jobeet Tutorial and my Journey Down the Rabbit Hole

Posted by ryan in jobeet, php, symfony


Reflections on Working Alone

alice in her slightly-psychedelic adventures in wonderland

Honestly, I'm just an orphan web developer who's easily distracted by the latest idea. I have no computer science degree (math & spanish) and have stupidly only ever worked by myself (my company: blue door project). I've gone down a lot of fruitless paths and, basically, wasted many hours on worthless details. It's part of being a nerd, but all that wasted time is still kind of a bummer.

If you're like me, take a break guy. Go outside and have a deep breath - it's nearly spring. When you come back, we'll talk more about Symfony, Jobeet and how I stay on track in a fast-changing industry. This is my story of journeying down the rabbit hole, and finding a way back to reality with Symfony.

We all need a mentor and I guess mine is the Symfony framework itself. The Symfony book, original Askeet tutorial, Symfony source code, and, most recently, the Jobeet tutorial have coached me from a being a below-average developer to, at the very least, one that understands the framework and codes with greater consistency.

My latest journey (going through the new Jobeet tutorial) was just as eye-opening as the original trip that introduced me to frameworks in general. This time around, I discovered numerous mistakes and poor choices that I've made consistently for months. Even more exciting, I discovered a few new tricks that Symfony has learned.

My Journey through Jobeet

Last week I finally decided to go through Symfony's new Jobeet tutorial. To get the full effect (I recommend this), I created the project on my local computer - manually writing most of the code instead of copying and pasting. The end effect was a feeling of renewal and rejuvenation. Below are some of the highlights.

The check_configuation.php Script [see this see it]

This is a tiny detail, but it caught a few things on my computer, so I feel the need to pay homage to it. Apparently there's a little check_configuration.php script that makes sure that you're computer is setup correctly to run Symfony. Despite the fact that I've been running Symfony sites from my laptop for years now, this script highlighted a few things I had overlooked. So, give it a try, you may be surprised.

Where to Place your Symfony Library Files? [see this see it]

One of the first things I do to a project (I use subversion) is to bring both the symfony lib and symfony data directories into my project via svn:externals. Following suit with the project:freeze task, I've always organized my library in the following ways:

   svn propset svn:externals "symfony http://svn.symfony-project.com/tags/RELEASE_1_2_4/lib" lib
   svn propset svn:externals "symfony http://svn.symfony-project.com/tags/RELEASE_1_2_4/data" data

The Jobeet tutorial, however, instructs the user to setup the libraries in a different directory (/lib/vendor/symfony).

  svn propset svn:externals "symfony http://svn.symfony-project.com/tags/RELEASE_1_2_4" lib/vendor

It's a small detail and a matter of taste, but I like the feel of having the symfony library in the vendor directory. But this one, is completely up to you.

User Stories [see this see it]

As a one-man band, my lack of planning has consistently punished me over the years. It was nice, then, to see the user stories associated with the Jobeet tutorial. I now draw a similar looking browser at the top of my user-story drawings not because it's productive, but because it's cute. And hey, drawing should be fun.

Schema.yml - Very Explicit [see this see it]

The format of the schema.yml struck me by being much more explicit than what I had been writing. This really boiled down to 2 things, both of which I liked:

  1. Because of the forms framework, it pays more than ever to include attributes such as required and index: unique. By including these things in your schema, the form generators will correctly apply the correct type of validation. It saves time and makes things more consistent. Good call.
  2. Now that we're including more attributes, the YAML array format (e.g. { required: true, index: unique }) needs to be used more often. In the Jobeet tutorial, every column uses this format, even when it's not necessary. The result is a more consistent-looking file.
# goodbye old format
my_column:         varchar(255)

# hello new format
my_column:         { type: varchar(255), required: true }

Numbering your Fixture Files [see this see it]

Symfony loads your fixture files in alphabetical order. This is important because, in the case of foreign key relationships, you need to load the parent record before the child record that references it. To control this, Jobeet mandates the prefixing of these files with a number like 010 or 020 (e.g. 020_categories.yml). This gives you control over the order in which your fixture files are loaded, without needing to sacrifice your descriptive filename.

sfPropelRoute & sfPropelRouteCollection - Less Code, More REST [see this see it]

Perhaps the most important reason to go through the Jobeet tutorial is to learn the new form framework. One of the most compelling improvements are the new sfPropelRoute and sfPropelRouteCollection. These two new routing classes allow for the following advantages over the old system:

  • Less routes (using sfPropelRouteCollection). All of the object_new, object_edit, object_create, object_list, etc routes that accompany any normal CRUD can now all be handled using just one route.
  • The removal of boilerplate code in your action to load your object (e.g. via the passed id parameter) and forward to 404 if the object wasn't found. With sfPropelRoute, your object is automatically loaded and ready for your use in the action without any code. This is flexible and objects can be automatically loaded based on any column (not just your primary key). Taking it one step further, if the object can't be found, the route will forward the user to a 404 page automatically. The first 2 lines of many of your actions can now be erased.
  • Flexibility with your urls. Since you pass your entire Propel object as an argument in url_for, you have more flexibility to change the format of your urls in the routes.yml file without needing to change the url_for calls in your template. For example, in the old system, if you're currently passing the id column as a parameter in your route (url: /my-object/:id) and want to change it to use the slug, this can be done simply by modifying the route in routes.yml. Before, you'd need to first modify routes.yml and then all of your url_for calls to change the parameter from id to slug (from '@my_route?id='.$id to '@my_route?slug='.$slug).
  • A more RESTful structure. By default, the sfPropelRouteCollection set of routes use the PUT and DELETE methods where appropriate. For someone still getting used to the REST idea, this is a great way to see how it should be done correctly.

The app:routes task [see this see it]

Symfony now comes packaged with a new app:routes task that lists all of the routes for a particular application. Why is this important (I wondered that myself at first)? It's important for 2 reasons. First, because of the new sfPropelRoutesCollection routing class (more abstractly, the sfObjectRouteCollection class), one entry in routing.yml may now actually represent multiple routes. The app:routes framework spells all of these routes out. Second, Symfony 1.2 is a much more RESTful framework, meaning that the use of PUT and DELETE methods are more integrated. The app:routes task allows you to much more easily view your routes and the specific method(s) required to match each route.

sfValidatorFile - Some Cool Improvements [see this see it]

File uploading using the new form framework left a little to be desired in Symfony 1.1. Many improvements have been made in version 1.2. For example, here's one use of the validator sfValidatorFile from Jobeet:

$this->validatorSchema['logo'] = new sfValidatorFile(array(
  'required'   => false,
  'path'       => sfConfig::get('sf_upload_dir').'/jobs',
  'mime_types' => 'web_images',
));

One of the improvements is the fact that sfValidatorFile will now do the complete job of saving the file to the server for you. To specify the location where you want the file saved, you can pass the "path" option. The 'mime_types' option (available since version 1.1), is a shortcut to validating your file type.

The directory where the file will be saved is given by the "path" option. But where does the actual filename of the to-be-saved file come from? By default, the sfValidatorFile validator initializes and passes all the pertinent information to the sfValidatedFile validator, which is responsible for doing work such as actually saving the file. By default, the filename will be generated via the sha1 method giving you a unique, but random-looking filename for your uploaded file.

Fortunately, you can easily override the naming of your file so that your filenames are meaningful. Imagine (as is the case in Jobeet) that you're uploading a file for a column called "logo" in one of your Propel models. To override the naming of the saved file, simply create a method called generateLogoFilename() inside your Propel model. Your new method may look something like what I have below where I try to save the file using the original filename, or at least something similar:

  public function generateLogoFilename(sfValidatedFile $validator)
  {
    // if no file by the original name already exists, just return the originally uploaded filename
    if (!file_exists($validator->getPath().'/'.$validator->getOriginalName()))
    {
      return $validator->getOriginalName();
    }
    
    // iterate until we find a unique filename of format original_filename-n.ext where n is an integer
    $i = 1;
    while (1)
    {
      $newFilename = sprintf('%s-%s.%s', basename($validator->getOriginalName()), $i, $validator->getOriginalExtension());
      if (!file_exists($validator->getPath().'/'.$newFilename))
      {
        return $newFilename;
      }
      $i++;
    }
  }

form_tag_for - Creates your Opening Form Tag and More [see this see it]

Those of you who've been around Symfony for awhile will remember the form_tag() helper function that was originally used to open

tags in Symfony 1.1. This tag has been replaced in an important way in Symfony 1.2 with the form_tag_for() function. Whereas the former form_tag() helper function simply outputted a form tag with an appropriate action url, the new form_tag_for takes the sfForm form as an argument and accomplishes much more. Specifically, the form_tag_for function will automatically add the enctype="multipart/form-data" attribute when necessary for uploading files. Furthermore, the form_tag will appropriately set the method attribute and add any hidden tag necessary to "fake" the PUT method. This means that the form_tag_for function gets you to a RESTful state, automatically.

Using the Delete Method and checkCSRFProtection [see this see it]

Part of being RESTful is using the DELETE HTTP method when deleting objects. This is easy with the new sfPropelRouteCollection set of routes which sets this up correctly for you. What's cool about this is that, when creating a link using the DELETE method, CSRF protection is automatically added to your link. For example, to delete an object, we might create a link like this:

<?php echo link_to('Delete', 'job_delete', $job, array('method' => 'delete', 'confirm' => 'Are you sure?')) ?>

The above code will do 2 special things. First, assuming javascript is available, the link will be wrapped in a form and submitted with the sf_method parameter DELETE to force the delete method. Second, a CSRF token will automatically be added to the link. So, even in cases where an sfForm isn't officially being used, you can still use the CSRF protection. In your delete method, you can simply add the following line to respect this CSRF protection:

$request->checkCSRFProtection();

The New Admin Generator [see this see it]

The new Symfony admin generator is sweet. It successfully accomplishes everything that the old system did (with a familiar YAML configuration), but much more. This section of Jobeet is a must read. Specifically, be sure to check out the batch_actions, object_actions, and actions section where Jobeet shows you how to easily extend the functionality of your admin interface.

Zend Integration - Sending Email and Integrating Search (Lucene) [see this see it]

For me, the most fascinating part of Jobeet was seeing the ease with which the Zend framework was integrated and used inside the Symfony framework. Its integration consists of just a few lines of code inside your ProjectConfiguration class. The results, among other things, are an easy way to send email and access to the super-powerful search engine Lucene (Zend's port of JAVA Lucene).

Sending emails (see here) is a snap and I'm excited to learn about the additional features such as sending via SMTP and the text body for emails.

Integrating the Lucene search engine (see here) is a little bit more involved, but still ridiculously easy. Previously, I had been using the sfSearchPlugin (this plugin is still alpha - and I admittedly was using it before I should have) and am happy to see the simplicity with which Lucene can be integrated directly into Symfony. Under the surface there's a lot more to learn about Lucene's query format, but this is definitely the direction I'll be going with my search.

sfFormExtraPlugin - I FINALLY Checked it Out [see this see it]

sfFormExtraPlugin is a plugin developed and maintained by the Symfony core team. This should give you the confidence that it is both reliable and represents the type of code that the Symfony devs intended for sfForm. The best way to check out everything that this plugin can do is to read about it directly. Especially if you use Jquery (which I do), you'll find this plugin fantastic.

Closing Thoughts

As a developer (especially one who works alone) attaching yourself to a framework with strong code supported by a large community is a fantastic way to make sure that your development ideas and best-practices are staying on track. The Jobeet tutorial is a huge look into the minds of the Symfony devs and what they see as best practices for Symfony. While there's always leeway on what's wrong and what's right, by sticking to a great framework like Symfony you're able to stand on the shoulders of giants and reduce the number of poor decisions that you'll make. For me, the Jobeet tutorial was a wake-up call for several things that I was doing poorly, if not incorrectly altogether. Good code helps you stay on track in a world where clients demand a lot, and technology changes rapidly.

Thanks for the shares!
  • StumbleUpon
  • Sphinn
  • del.icio.us
  • Facebook
  • TwitThis
  • Google
  • Reddit
  • Digg
  • MisterWong