Propel is not hard anymore

The two most common criticisms I hear about about Propel are the difficulty to learn the Criteria object, and the amount of code required to do a custom Join. That, and the fact that Propel is slow--but Propel 1.3 will just leave every other ORM behind, so speed is not a good reason to ignore Propel, especially now.

The abilities of Propel are just amazing, but the API is far too Java-inspired for our hectic programming speed. Rapid Application Developers need simple APIs that allow them to go fast in the majority of the cases. That's the philosophy of the sfPropelFinderPlugin, that I already introduced in a past post. This symfony plugin (which doesn't depend on symfony - you can use it with Propel alone, provided you load the Propel classes by hand) recently got better.

No more Criterions

sfPropelFinder encapsulates Criteria calls, so you never need to manipulate the complex Propel syntax. Instead, the plugin proposes a syntax reminiscent of SQL to retrieve Propel objects:

$articles = sfPropelFinder::from('Article')->
  where('Title', 'foo')->
  _and('PublishedAt', '<', time())->
  _or('Title', 'like', 'bar%')->
  find();


If you already tried to do AND and OR queries with Criterion objects, you know how the above can be long and complicated to write. Here, the Propel finder does all the dirty job for you. Another relief is the use of the CamelCase version of the properties names, which is consistent with the Propel object getters. You no longer need to type infamous strings looking like ArticlePeer::PUBLISHED_AT; PublishedAt will do the trick.

Easy joins

Another recent improvement of the finder plugin is its ability to deal with related objects in joins and hydrating. The idea is that you define relationships in the schema, with foreign keys and references, but Propel keeps asking you to repeat them each time you want to join two objects in a Criteria addJoin(). Since you don't like to repeat yourself, sfPropelFinder will quietly check the Propel TableMap to explicit a join if you write:

$postsToRead = sfPropelFinder::from('Post')->
  join('Author')->
  where('Author_Name', '<>', 'Joe')->
  find();


Wait, there is more. The finder can understand complex joins between several tables. Imagine that an Author has a Status. If you want to get all Posts for the Authors with a given Status, just chain join() calls. You get the speed of ActiveRecord-like syntax with the robustness of Propel code behind.

$postsToRead = sfPropelFinder::from('Post')->
  join('Author')->join('Status')->
  where('Status_Name', 'polite')->
  find();


Reduce queries, but don't increase code in return

Lastly, if you rely on Propel's generated doSelectJoinXXX() methods to reduce query count, you are probably often frustrated that the method you need is never among the generated ones. And if you ever tried writing such a method yourself, dealing with composite resultsets and complex hydrating, you probably upgraded your database for more raw power instead of pulling your hair off.

Fortunately, sfPropelFinder offers a convenient with() method allowing you to list the objects you want to be hydrated together with the main object. The following code issues only one database query:

$latestPosts = sfPropelFinder::from('Post')->
  with('Author', 'Status', 'Category')->
  orderBy('PublishedAt', 'desc')->
  find(10);
foreach($postsToRead as $post)
{
  echo $post->getTitle(),
       $post->getCategory()->getName(),              // Post's Category is already hydrated: no query
       $post->getAuthor()->getName(),                // Post's Author is already hydrated: no query
       $post->getAuthor()->getStatus()->getName();   // Author's Status is already hydrated: no query
}


Conclusion

I hope you find this syntax easier to use than Propel's native methods. The sfPropelFinder plugin gets better and better every day thanks to the contributions of several developers, and is already a good alternative to Peer classes and Criteria calls. This should give you time to deal with the more important stuff.

Possibly related posts (automatically generated):

36 Comments so far

  1. FX Poster on April 11th, 2008

    As I see, there is no ability to make left/right joins now, am i right?

    I'd like something like this:

    $latestPosts = sfPropelFinder::from('Post')->
      with('Author', array('Status', Criteria::LEFT_JOIN), 'Category')

    My implementation of that kind of joins is here.

  2. Stefan on April 11th, 2008

    ooohh... especially that 'with' is awesome. I love that. The simplicity of fluent interfaces also rocks.

  3. NiKo on April 11th, 2008

    Many thanks François, this is a huge usability enhancement for Propel syntax victims :-)

  4. Dynom on April 11th, 2008

    How does this work with parenthesis ?

    SELECT id,username FROM user WHERE ( field1 > 100 AND field2 = 7 ) OR ( field1 = > 50 AND field2 = 8 ) OR field2 = 9

    Since that seems to be the struggle with most other query builders.

  5. D@ Mick on April 11th, 2008

    I have used Propel for most of my projects, but I just started a new one using the sfDoctrinePlugin. It's not that Propel is a bad ORM layer, but there are still a few shortcommings to it. What about symantics? If you look at the verbosity of Propel according to Doctrine? You need more time spending in XML and/or YAML files. Propel doesn't support his own Query Definition Language. And what about eager loading in your queries, inheritance, mixins, full text-searching, query/result caching? Doctrine is build in a logical way, the pragmatic use of ActiveRecord mixed with the power of Hibernate. So in my opinion, Propel still has to make some big core improvements to get back on track.

  6. Ian P. Christian on April 11th, 2008

    Nice work!

    This will make propel slightly less painful to use until doctrine is in a stable state! But, like I said in a previous post - this does not make doctrine redundent, it does however close the gap a little.

    DQL is great for sometings, and there's of course support for event listeners and inheritence.

    I've been forced to use Propel in some of my contract work, so i'm very greatful for this :)

  7. naholyr on April 11th, 2008

    If this works really as shown, this with() method is a pure miracle :D This is the graal I was waiting for such a long time. This interface is elegant, and efficient. Very nice.

    I will check this out tonight, but I still have the same questions as precedent users : - What about the type of join (left, right, etc...) ? - What about the handling of parenthesized conditions ?

  8. Francois Zaninotto on April 11th, 2008

    Guys, guys, please let me breathe!

    Ok, the Propel finder does not handle And and Or with parenthesis, nor does it allow to do a with using a left or right join... for now. These features are in the roadmap, though.

    But let me remind you that this plugin didn't exist half a month ago, and that you can already use it for 80% of the object fetching needs of your applications. Writing code, tests and documentation takes time, and I have a job.

    Now, if you REALLY want to make additions to the plugin, I'm open to any good quality patch, with unit tests and documentation.

  9. naholyr on April 11th, 2008

    Don't worry, that was only questions (like curiosity), not a request ^^

    You did great job with this plugin, thinking to apply sfFinder syntax to Propel was brilliant.

    I'll see if I can help in any way (I'll think about the way you could handle parenthesis as an interface, even this is not obvious).

    For the joins, in fact you do handle them (in join() method ;), I was not asking for the with() method) : ->join('Author', 'LEFT JOIN')

    You could automatically detect if a LEFT or INNER join is the better choice, based on the column's isNotNull() (you can check out nahoPropelOptimizer I did this to fix the fact that Propel always does default INNER JOINs).

    And again, thanks, very good job :)

  10. Taku on April 11th, 2008

    That's just great ! You did something amazing François, using propel will now be as great and as painless as it is with doctrine.

    Really : wow !

    Could the next releases come very fast :D

  11. eric on April 12th, 2008

    I agree with Ian's sentiment. these features (and your nice work, Francois) will make propel more tolerable. But the new chaining query syntax appears to be poor mimicry of php doctrine's DQL. i look forward to the (hopeful) day when your framework switches its official ORM to doctrine! but, in the meantime, you're doing good work with what you're stuck with. many thanks.

  12. FX Poster on April 12th, 2008

    I have one more question - have you tested your plugin with Propel 1.3?

  13. Skyblaze on April 13th, 2008

    i also want to know if it works for propel 1.3 and another question. If i use the with() or join() method do i obtain he same result? I mean that underneath it generate an sql join in both cases right? Then if i use the join() method (maybe on 2 or more models/tables) which method/fields i've in the resulting object/objects? This isn't so clear in the docs thanks

  14. François Zaninotto on April 13th, 2008

    @Skyblaze & FX Poster: probably not, since I use Resultsets and I think it changed in Propel 1.3. But to make it work for Propel 1.3, I think that all it takes is 5 minutes.

    @Skyblaze: Both do a SQL join, the difference is that with a with, the columns of the joined tables are added to the select, so that you can hydrate the related objects. It's exactly the same as with Propel.

  15. Skyblaze on April 13th, 2008

    i still don't understand the difference. anyway what do you think about the propel generated "doSelectJoinXxx" peer class static method? We can use it for all situations and it overcomes all the problems

  16. FX Poster on April 13th, 2008

    A difference between with and join can be shown on a simple example:

    $latestPosts = sfPropelFinder::from('Post')->
      with('Author', 'Status')->
      where('Status_Name', 'polite');

    The resulting SQL is:

    SELECT Post., Author., Status.* FROM Post, Author, Status WHERE Author.post_id = Post.id AND Author.status_id = Status.id AND Status.name = 'polite'

    $latestPosts = sfPropelFinder::from('Post')->
      join('Author', 'Status')->
      where('Status_Name', 'polite');

    SQL is:

    SELECT Post.* FROM Post, Author, Status WHERE Author.post_id = Post.id AND Author.status_id = Status.id AND Status.name = 'polite'

    As you see - in the second example, tables are joined but they are not in the SELECT clause.

  17. Skyblaze on April 13th, 2008

    in the second (join() method) example we don't select the columns of the ther 2 tables to be retrieved right? Only all the columns of the post table is this exact? But then i ask nyself what is the difference in performance....is it so great to justify the two methods/sintaxes? And then another question. If i use the with() method i don't have to chain also the join() method right? Thanks

  18. Skyblaze on April 13th, 2008

    I would like to know if it will be implemented some nice method to support many to many relationships also 'cause in propel there isn't a nice API for them thank you

  19. FX Poster on April 13th, 2008

    Difference in performance (second example vs first) is hmmm... not critical. I don't want to benchmark it... So - if you are not sure - whether you need joined record - you better use with (and join and hydrate them). But if you need ONLY records from Post table - you may use join.

  20. FX Poster on April 13th, 2008

    François, can you install a "Subscribe to comments" plugin for your blog? :)

  21. Skyblaze on April 13th, 2008

    Anyway i think that this plugin for now (don't get me wrong i think this plugin is amazing and i will defenitevely use it) lacks of two features to me to use it in a production system. Many to many relationships support and "And" and "Or" clauses with parenthesis. Whith these 2 addons thins plugin will defenitevely replace propel api :)

  22. FX Poster on April 13th, 2008

    What do you mean when you say "Many to many relationships support"? This is Finder plugin. Try to understand it.

    To make a full support of many-to-many relationships we need more than this plugin. Much more.

    But it is already suitable for retrieving such relationships.

  23. Skyblaze on April 13th, 2008

    i mean that for now in propel to retrieve records of many to many relationships you have to do this: getBookReaderRefsJoinReader(); foreach($readerRefs as $ref) { $reader = $ref->getReader(); // <-- this isn't an additional query // since we used the Join method above } }

  24. Skyblaze on April 13th, 2008

    sorry i mean this: getBookReaderRefsJoinReader(); foreach($readerRefs as $ref) { $reader = $ref->getReader(); // <-- this isn't an additional query // since we used the Join method above } }

  25. Skyblaze on April 13th, 2008

    this:

    getBookReaderRefsJoinReader();
      foreach($readerRefs as $ref) {
        $reader = $ref-&gt;getReader(); // &lt;-- this isn't an additional query
                                     // since we used the Join method above
      }
    }

  26. Skyblaze on April 13th, 2008

    i don't know how to put php code in here :(

  27. Skyblaze on April 13th, 2008

    anyway how can i access related objects in a many to many relationship with this plugin?

  28. François Zaninotto on April 13th, 2008

    @Skyblaze, FX Poster: I suggest that you continue the discussion on the users mailing-list or in the forum.

    @Skyblaze: I really recommend you to read all the available documentation about Propel and ORMs available at the symfony website. It's a prerequisite for using Propel, and the Propel Finder. Things will appear much clearer afterwards.

  29. Gilles D. on April 14th, 2008

    Woow, look great!

    Like others, I'm really interested for the with method. I will try it.

    Thank you François.

  30. Eric Lemoine on April 14th, 2008

    Nice work ;)

    However it does not currently handle multiple FK from the same referenced table. Indeed Propel name the methods with a "RelatedByxxx" suffix name ;) So you will have to adapt your current "relateObjects" method to take that in account.

  31. Skyblaze on April 15th, 2008

    I wonder why you should use multiple fks to the same referenced table in the same origin table

  32. Taku on April 20th, 2008

    François, do you manage to release this wonderful plugin for Symfony1.1 soon ? :-)

  33. Markus.Staab on April 21st, 2008

    @Taku:

    as described in the article, the plugin is symfony independet,.. so i guess it should already work with symfony 1.1

  34. lazymanc on April 22nd, 2008

    @Skyblaze: An example would be if you had a "news" table and wanted the fields "created_by" (original author) and "modified_by" (the last user to change the news article) that were both FK's to a "user" table.

  35. Stephen Riesenberg on May 3rd, 2008

    With my understanding of doctrine, and now seeing the next gen of java orm's (java persistence api), I think this needs more spice. However, it's better than criteria. Interesting work.

  36. Martin Poirier Théorêt on June 13th, 2008

    I did check all the code and it's pretty clean ! I have the same concern has lazymanc, I must also need to "use multiple fks to the same referenced table in the same origin table" so how do plan to fix this?

    Also I do have a really weird (but inovative) DB structure and I would like to join Model on a non Foreign Key basis, I do think I know how to extend your system but do you have an idea how ?

    Feel free to email directly (or contact me on any other suggested way) if you want to discuss this.

    Any thanks for the finder you save me a couple days of work !