Reflections on Working Alone
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 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 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 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 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:
- 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.
- 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 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 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 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 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 it]
Those of you who've been around Symfony for awhile will remember the form_tag() helper function that was originally used to open