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->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->add(ArticlePeer::AUTHOR, 'John');
$articles = ArticlePeer::doSelectWithI18n($c);
The sfPropelFinder offers an alternative way to do this:
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:
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:
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:
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.
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 = 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.
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:
- sfPropelFinder is like jQuery for Propel
- Propel is not hard anymore
- Finding “Augmented” Propel Objects
- A Finder Smarter Than Propel Getters
Possibly related posts (automatically generated):
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
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.
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 ..
@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.
What an amazing job you're doing. Thanks for all these improvements.
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.
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.
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.
@broesch: Which revision is buggy? The head has moved...
Thanks for all this amazing job. You are making our's life easier!
The bug first appears in 10101
@broesch: I can't reproduce it. Could you file a Trac ticket with your schema and the query you're testing?
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.
Any ETA on a 1.1 compatible plugin?
@Cristiano Betta: It is already Propel 1.3 and symfony 1.1 compatible since r10085.
Yeah but I can't seem to install it using the new plugin command
@Cristiano: You'll have to checkout the svn version, the latest available package is not compatible with symfony 1.1.
hi
I have one question. What about differences in performance [Propel Creole vs. sfPropelFinder] ?
@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
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
@Cristiano: You must use subversion... This is too long to explain in a weblog comment.
Yeah, I figured it out already. Use svn:external. Thanks anyway though.
@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
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.
@forkmantis: Yes, the finder uses
doSelectRS()to find objects, and so it triggers behavior hooks for selection. But I see that the finder'sdelete()usesdoDelete(), and this does not trigger deletion hooks. I'll open a ticket about it, thanks for the report.@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.