Archive for June, 2008

A Finder Smarter Than Propel Getters

The sfPropelFinder symfony plugin keeps getting better. The addition of two new methods, relatedTo() and findLast() make things even easier than before. Let's see it with an example.

Propel is smart enough to generate getter methods for related objects when you define a foreign key in your schema. For instance, if your schema relates Comment to Article as follows:

propel:
  article:
    title:      varchar(255)
    body:       longvarchar
    created_at: ~
  comment:
    article_id: ~
    author:     varchar(100)
    body:       longvarchar
    created_at: ~

Then after you build your model, the BaseArticle class will provide a getComments() method that will facilitate the retrieval of the comments related to an existing article:

// Getting all comments
$comments = $article->getComments();


But now, what if you need to get the list of comments ordered from the last posted to the first posted? The generated getter method accepts a Criteria as its first parameter, so you can write:

// Getting all comments, ordered by date
$c = new Criteria();
$c->addDescendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = $article->getComments($c);


If you want the latest comment posted on an article, you will need to make something slightly more complicated. For the sake of the example, the code appears on the same fashion as the previous ones, but you should definitely wrap it up in a method stored in the Article class.

// Getting the last comment, ordered by date
$c = new Criteria();
$c->addDescendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = $article->getComments($c);
if(isset($comments[0])
  $comment = $comments[0];
}
else
{
  $comment = null;
}

// Alternative way, but not really shorter
$c = new Criteria();
$c->add(CommentPeer::ARTICLE_ID, $article->getId());
$c->addDescendingOrderByColumn(CommentPeer::CREATED_AT);
$comment = CommentPeer::doSelectOne($c);


The sfPropelFinder provides an alternative, and I believe better way of doing this. The finder object can filter results related to a given object thanks to its new relatedTo() method. This method uses the schema to guess the local and foreign columns, so you don't have to pass any other argument than the related object:

// Getting all comments
$comments = sfPropelFinder::from('Comment')->
  relatedTo($article)->
  find();


How is that better than $article->getComments()? It is not. But for the following examples, the interest of the relatedTo() method appears more clearly:

// Getting all comments, ordered by date
$comments = sfPropelFinder::from('Comment')->
  relatedTo($article)->
  orderByCreatedAt()->
  find();


// Getting the last comment, ordered by date
$comments = sfPropelFinder::from('Comment')->
  relatedTo($article)->
  findLast();


findLast() is also a recent addition to the plugin. It returns a single record, the last one, based on the creation date (or on the id if there is no creation date column).

The relatedTo() method offers the same convenient way to retrieve related objects by a single method call as Propel generated getters. But since it is embedded in the sfPropelFinder class, it allows for further manipulation of the results with a very simple API.

So if you didn't give it a try yet, checkout the sfPropelFinderPlugin from the symfony Subversion repository. You will soon see how it dramatically reduces the amount of code you write in the model.

Finding “Augmented” Propel Objects

Retrieving objects with Propel is easy with sfPropelFinderPlugin. And, since the addition of the with() method, you can hydrate related objects in a single query with no pain.

But there is still a use case where the sfPropelFinder can't help you, and this is when you need to add one or two columns from a related objects but not the whole object.

For instance, imagine that you have an Article and a Comment object, the Comment being related to the Article in the schema by a article_id column.

Until now, if you wanted to retrieve some comments and the related article title with no additiona query, you had to either get a resultset and parse it by hand, or hydrate both the Article and the Comment object:

$comment = sfPropelFinder::from('Comment')->
  findOne();
echo $comment->getArticle()->getTitle(); // requires an additional db query
$comment = sfPropelFinder::from('Comment')->
  with('Article')->
  findOne();
echo $comment->getArticle()->getTitle(); // no additional db query, but a whole object is hydrated just for one column


Today, the sfPropelFinder just made it simpler. You can add a single column from a related object with the withColumn() finder method, and retrieve it afterwards on any Propel object with the getColumn() method:

$comment = sfPropelFinder::from('Comment')->
  withColumn('Article_Title')->
  findOne();
echo $comment->getColumn('Article_Title'); // no additional db query, and only one more column fetched


Internally, the finder will look for a relation between the Comment and the Article classes in the TableMap, and add the related join in its internal Criteria. It will call the Criteria's addAsColumn() method and deal with the augmented resultset so that the additional column is available in the Comment objects.

This new method accepts calculated columns through raw SQL code, and alias names. That means that you can add a column and reuse it in an orderBy() clause:

$articles = sfPropelFinder::from('Article')->
  join('Comment')->
  withColumn('COUNT(comment.ID)', 'NbComments')->
  orderBy('NbComments')->
  find();


Not only will the resulting Article objects be ordered by number of comment, but each individual object will have access to its number of comments through getColumn(). All that with a single SQL query and a very short syntax.

So the number of situations where you need to get a resultset instead of an array of Propel objects has reduced again. the sfPropelFinder takes care of all the dirty job for you, and lets you focus on the design of your business model rather than the details. If you didn't test it already, it's time to get the HEAD SVN revision of sfPropelFinderPlugin and give it a try.

Is PicLens Malware?

I recently installed the PicLens Firefox extension. It is an incredibly useful way to browse image collections, the interface is both very responsive and well thought, and the integration into existing websites is unobtrusive enough to convince me.

Then, as I was monitoring requests on one application I develop on my local server, I noticed that each time I requested a page, two requests were received by the web server (in addition to requests for web assets such as JavaScript, CSS and image files). After investigation, I realized that the PicLens extension detected a <link> tag in the page content, and automatically fetched the RSS feed linked by that tag. It does so everytime it detects an application/rss+xml link.

I made the test with pages including more than one RSS feed (try php.net for instance) and noticed the same behavior, only at a larger scale. So PicLens does basically what Google Web Accelerator does: it prefetches web resources (in this case: RSS feeds) to accelerate the navigation experience.

I emailed the PicLens support about the issue, and here is their response:

Hi Francois,

Thank you so much for your kind words and for using PicLens! We really appreciate you taking the time to send us your thoughts.

I'm sorry to hear you are worried about PicLens's prefetching behavior. We prefetch all tags that have a content type of "application/rss+xml" because we use that to match up mediarss feeds with items on the page. It's not a bug at all, nor have we heard of it causing any problems for anyone. Is there a specific reason you feel that it jeopardizes websites?

Hope to hear back from you soon.

All the best, Meg & The PicLens Team

I can think of many reasons why link prefetching is bad, among which wrong statistics, additional bandwidth and server load. But maybe I'm being too extremist on that one. What do you think? Can prefetching be considered as an acceptable practice nowadays? Or is the PicLens extension something that should not be installed?

Analyze your data

If you have been running a website for some time, you probably have a lot of data waiting to be analyzed. I'm not talking about server logs - you should already have a tool to follow these. But the data from your users, recorded in the database, this is very valuable data that you can learn from.

If you included a created_at or an updated_at column in your tables, then you're almost done. All you have to do to analyze the data is to query your database. Then you can see how many forum messages were posted day by day since the last month. Or the progression of your demo application usage. The ideal would be to display the data in a nice line chart, and to provide navigation and filtering controls to easily browse your statistics.

You have the data, all you need is a tool to visualize it. This tool is now available as a symfony plugin. I have the pleasure to release today a plugin called sfStatsPlugin, which does exactly that. Based on a simple YAML configuration file, the plugin will take advantage of the methods from your Propel Object Model to display a nice line chart from Google Chart API or a jQuery flot. It is fully i18ned, compatible with symfony 1.0 and 1.1, and released under the MIT License, thanks to my company's sponsoring.

sfStatsPlugin

If you have some data waiting to be analyzed, download the plugin and try it right away. In minutes, you will understand how your site usage is evolving, with all the precision allowed by your object model structure.

Browsing your assets with style

I have the pleasure to introduce a new plugin called sfAssetsLibraryPlugin. See it as a media library on steroids.

State of the art

Many symfony projects use the sfMediaLibraryPlugin in their backend application. Until today, it was the only publicly available plugin for managing images and other assets in a symfony application via a web interface.

But this plugin had limited abilities in terms of file and folder management. You could rename a file, but that's about all. Worse, it didn't allow you to add metadata to your assets. Managing a large image library without ever dealing with copyright or legend is almost impossible.

Lastly, every CMS-like application needs a robust asset management utility, hooked into rich text editors, to allow writers to deal with images without pain. I saw numerous tweaked versions of the sfMediaLibrary made for that purpose, all reinventing the wheel of the 'advanced media library'.

One step forward

Today, my company releases a new media library plugin called sfAssetsLibraryPlugin. It is a complete rewrite of the initial media library plugin. It introduces a database layer in addition to the asset files stored under your web root, adds a ton of new file management features, and was built with customization in mind.

Add files on-the-fly in the directory your're browsing, rename files and folders, move entire folders and their content alltogether, find assets by filename, upload date, author, description or copyright, integrate tightly with TinyMCE... That's only some of the features of the plugin.

Oh, and if you have an existing sfMediaLibrary, the new plugin will import your assets as soon as you call the bundled synchronize pake task.

Of course, you can change its look and feel (below is a screenshot of the plugin integrated in our backend) and add your own metadata columns with sfPropelAlternativeSchemaPlugin. Our implementation adds tagging abilities to assets, and it takes only a couple more lines in the application code, without ever touching to the plugin code. We made sure the plugin was very clean and easy to extend.

customized edit view

The plugin has currently been used in my company for several months, and even if tagged as beta, you can download it right away. Of course, it has I18n, unit tests, PHPDoc and a text documentation - you should consider these as the standards of symfony plugins. The sfAssetsLibraryPlugin is released under the MIT license, for your sharing pleasure.

Who did it

The plugin is the work of William Garcia, Gabriele Santini and myself, and it took us quite some time to finalize the plugin so it is truly customizable and powerful. We will keep on maintaining the plugin in the future, so expect more features in the upcoming months (drag and drop files to move them, for instance).

I want to express my warm thanks to both William and Gabriele for their work. I am very proud of the result, and I believe it is a huge step forward in the 'build your application with plugin legos' direction.

Of course, feedback is welcome. If you find a bug, please file a ticket in the symfony Trac with sfAssetsLibraryPlugin as component. And if you want to volunteer to enhance the plugin, file a ticket, too, and just wait for our green light to start working on a patch.