sfPropelFinder: Now With I18n, Pagination and Update Queries

Development goes on at a decent pace on the sfPropelFinderPlugin. Recent additions make it even more suitable for your needs. It should cover about 80% of the common use cases, so you'd better try it fast.

Compatible With I18n

Projects using symfony's internationalization (i18n) layer to store various versions of a record in the database based on the user culture can also use sfPropelFinder. As an alternative to doSelectWithI18n(), you can use the plugin's native with() method to hydrate the related i18n objects.

For instance, if you have an Article class with i18n for text columns:

article:
  id:         ~
  author:     varchar(255)
  created_at: ~
article_i18n:
  content:     varchar(255)

This structure allows you to define several values for the content column, directly on the Article object:

$article = new Article();
$article->setAuthor('John');
$article->setCulture('en');
$article->setContent('english content');
$article->setCulture('fr');
$article->setContent('contenu français');
$article->save();


You probably know how to request Articles and their i18n version in a single query with doSelectWithI18n():

$c = new Criteria();
$c->add(ArticlePeer::AUTHOR, 'John');
$articles = ArticlePeer::doSelectWithI18n($c);


The sfPropelFinder offers an alternative way to do this:

$articles = sfPropelFinder::from('Article')->
  where('Author', 'John')->
  with('I18n')->
  find();


Not ony is it faster to write, it also allows you to use i18n with other 'with()' hydratation clauses, or to retrieve a single object, which is impossible with the generated i18n methods:

$article = sfPropelFinder::from('Article')->
  where('Author', 'John')->
  with('I18n', 'Category')->
  findOne();


Just like doSelectWithI18N(), with('I18n') will select the version of the i18n content based on the current user culture. If you want to force the retrieval of a particular culture, you can use the withI18n($culture) synonym:

$article = sfPropelFinder::from('Article')->
  withI18n('en')->
  findOne();


with('I18n') and withI18n() without further arguments are synonyms. But, as I always forget whether the final 'N' of 'I18n' must be lower or uppercase, the with() version is permissive and will work with 'i18n', 'i18N', 'I18n' and 'i18N'. No more silly mistakes.

Update Queries

Thanks to a patch from an early adopter of the plugin named jug, sfPropelFinder now allows you to update several records in a row. This is something that required the use of BasePeer::doUpdate() in the past, now it is as simple as an associative array defining the columns to change:

$article = sfPropelFinder::from('Article')->
  where('Author', 'John')->
  set(array('IsRead' => true));


The set() method is a termination method similar to find(), count() or delete(), meaning that it doesn't return an sfPropelFinder object.

Beware that such a query will not trigger any of the behaviors registered on the save() method - it uses BasePeer::doUpdate() in the background. You can choose to force a record-by-record update by setting the second parameter of the set() method to true.

$article = sfPropelFinder::from('Article')->
  where('Author', 'John')->
  set(array('IsRead' => true), true);


Finder From A List Of Records

If, for any reason, you already have an array of Propel objects that you want to filter further, you can initialize a finder with this array. All the finder methods will work normally, based on the list.

$comments = $article->getComments();
$comments = sfPropelFinder::from($comments)->
  where('content', 'like', '%foo%')->
  orderBy('Author')->
  findLast();


This will result in a single query using IN () to limit the result to the original array.

Paginating A List Of Results

Using sfPropelPager is easy, only it takes a couple lines. Plus, it uses a Criteria object, and the purpose of the sfPropelFinder is to avoid using Criteria as much as possible. So how do you get a paginated list, i.e. a pager object, based on a finder? Simply call the paginate($page, $maxPerPage) termination method.

$pager = sfPropelFinder::from('Article')->
  with('I18n', 'Category')->
  where('Author', 'John')->
  paginate(1, 10);
// Use the usual pager methods
echo $pager->getNbResults();
foreach ($pager->getResults() as $article)
{
  echo $article->getTitle();
}


The setPeerMethod() is useless, since you can set the pager to hydrate related tables and columns with the with() method before paginating it.

Conclusion

It's only the beginning. sfPropelPager covers a large share of the object model retrieval use case, and the objective is to reduce the need for Criteria to less than 1% of the cases.

If you're interested in this plugin, make sure you read the previous posts on this blog:

Possibly related posts (automatically generated):

26 Comments so far

  1. Davert on July 2nd, 2008

    Thanks for your work. PropelFinder is very useful replacement for huge criteria objects. I'm impressed how compact and readable my code become with PropelFinder. If only I can replace all model stuff I wrote with this new syntax :)

  2. Florin on July 3rd, 2008

    Thanks for all your hard work in this direction, Francois. As a Doctrine aficionado, myself, this plugin makes me almost forget I'm using Propel for an ORM, when having to work with it.

  3. mcz on July 3rd, 2008

    Great plugin, makes using Propel a piece of cake :) But don't forget guys, Doctrine works similarly and also much more faster than Propel, also offers advanced built in pagination and much more ..

  4. Francois Zaninotto on July 3rd, 2008

    @mcz: Except that Doctrine isn't compatible with all the plugin symfony offers, and Doctrine is slower than Propel 1.3... for which this plugin will soon be made compatible.

  5. ghis on July 3rd, 2008

    What an amazing job you're doing. Thanks for all these improvements.

  6. Davert on July 3rd, 2008

    Yes, I choose Propel 1.2 instead of Doctrine because my server is running MySQLi and PDO don't have support for it. And also with Propel I recieve additional functionality like tags, comments, ratings just with several lines of code.

  7. Davert on July 3rd, 2008

    By the way, is there a way to exclude some columns from selection? Sometimes I don't need the whole record, just several fields from it, I suppose there can be made hook to plugin that can reduce the fields in selection.

  8. broesch on July 3rd, 2008

    Thanks for the great plugin. I think there is a bug in the head revision, it says Cannot fetch TableMap for undefined table ... I went back to revision 10085 and its working again.

  9. Francois Zaninotto on July 3rd, 2008

    @broesch: Which revision is buggy? The head has moved...

  10. neonard0 on July 3rd, 2008

    Thanks for all this amazing job. You are making our's life easier!

  11. broesch on July 4th, 2008

    The bug first appears in 10101

  12. Francois Zaninotto on July 4th, 2008

    @broesch: I can't reproduce it. Could you file a Trac ticket with your schema and the query you're testing?

  13. COil on July 4th, 2008

    Very cool François, for sure, i'll give it a try for my next project. I hope you will find solutions do handle the 20% of other use cases. :)

  14. Cristiano Betta on July 4th, 2008

    Any ETA on a 1.1 compatible plugin?

  15. François Zaninotto on July 4th, 2008

    @Cristiano Betta: It is already Propel 1.3 and symfony 1.1 compatible since r10085.

  16. Cristiano Betta on July 4th, 2008

    Yeah but I can't seem to install it using the new plugin command

  17. François Zaninotto on July 5th, 2008

    @Cristiano: You'll have to checkout the svn version, the latest available package is not compatible with symfony 1.1.

  18. PHP programmer on July 5th, 2008

    hi :) I have one question. What about differences in performance [Propel Creole vs. sfPropelFinder] ?

  19. François Zaninotto on July 5th, 2008

    @PHP programmer: sfPropelFinder user Creole, and introspects table relations at runtime while Propel alone uses code generation. So sfPropelFinder adds overhead on top of Propel.

    That being said, sfPropelFinder is compatible with Propel 1.2 and 1.3, and Propel 1.3 uses PDO instead of Creole, which makes it VERY fast. So I'd say:

    Faster ---- slower Propel 1.3 < Propel 1.3 sfPropelFinder < Propel 1.2 < Propel 1.2 sfPropelFinder

  20. Cristiano Betta on July 6th, 2008

    Mind me for asking, but how do I get to install from SVN?

    I tried

    symfony plugin:install http://svn.symfony-project.com/plugins/sfPropelFinderPlugin/

    But then I got

    Plugin "http://svn.symfony-project.com/plugins/sfPropelFinderPlugin/" installation failed: Package "http://svn.symfony-project.com/plugins/sfPropelFinderPlugin/" is not valid

  21. François Zaninotto on July 6th, 2008

    @Cristiano: You must use subversion... This is too long to explain in a weblog comment.

  22. Cristiano Betta on July 6th, 2008

    Yeah, I figured it out already. Use svn:external. Thanks anyway though.

  23. broesch on July 15th, 2008

    @Francois: Sorry for my late reply, I've been away. Regarding the bug, I can't reproduce it anymore, nevermind, maybe I had somthing wrong in my syntax. Thanks

  24. forkmantis on July 15th, 2008

    I see that doing a record-by-record update should invoke a model's behaviors. Will the "find" method also invoke a model's methods? It seems as if sfPropelFinderPlugin is ignoring my Paranoid behaviors.

  25. Francois Zaninotto on July 16th, 2008

    @forkmantis: Yes, the finder uses doSelectRS() to find objects, and so it triggers behavior hooks for selection. But I see that the finder's delete() uses doDelete(), and this does not trigger deletion hooks. I'll open a ticket about it, thanks for the report.

  26. forkmantis on July 16th, 2008

    @Francois: I was actually having trouble with my deleted records not being filtered out from Select queries. Here's the trouble I was running into. At one point in my project I had used the custom Peer builder from sfModerationPlugin. Later in the course of that project, I started using the AlternativeSchema plugin, and it's custom Peer builder. It looks like the Moderation builder adds several mixin hooks that affect select queries, where the alternative schema builder adds the *Behaviors.php files. I needed both of these things to happen, so I copied SfAlternativePeerBuilder to my lib, and extended the SfPeerBuilder from the Moderation plugin rather than the default one. After updating my propel.ini to reference the new one, everything seems to be working again. Not sure how much of an edge case this is, but it seems like it would be nice to have a single builder class that would work for any combination of the propel behavior plugins.