Reconciling Propel and Doctrine
Would you like to use Propel plugins in your Doctrine application, or vice-versa? For instance, our recent sfAssetsLibraryPlugin provides a great media management utility, but it requires Propel to use it. Thanks to recent progress on the sfPropelFinder plugin, you may soon be able to use it with doctrine.
Getting Abstract
My recent work on sfPropelFinder aimed at providing Propel with an easier requesting API than the current Criteria API. In order to do that, I took inspiration from other ORMs who provide fluid interfaces to the database layer (including Doctrine, Rails has_finder plugin and SQL Alchemy).
The resulting API facilitates object retrieval and modification with Propel - as much as PHP and the underlying Propel API allow it:
$article = sfPropelFinder::from('Article')->
where('Title', 'like', '%foo')->
leftJoin('Author')->
where('Author.Name', 'John Doe')->
orderBy('CreatedAt', 'desc')->
findOne();
This will look familiar to Doctrine users, since the Doctrine Query API works in a similar way:
$article = Doctrine_Query::create()->
from('Article a')->
where('a.title like ?', '%foo')->
leftJoin('a.Author u')->
where('u.name = ?', 'John Doe')->
orderby('a.created_at DESC')->
fetchOne();
The APIs are so similar that it is not very difficult to translate sfPropelFinder method calls to Doctrine_Query calls. So I tested this idea and created a sfDoctrineFinder class:
$article = sfDoctrineFinder::from('Article')->
where('Title', 'like', '%foo')->
leftJoin('Author')->
where('Author.Name', 'John Doe')->
orderBy('CreatedAt', 'desc')->
findOne();
Note: sfDoctrineFinder::leftJoin() is not yet implemented at the time of writing, but I keep the same code as above to make things clearer.
Internally, the code from Listing 3 is translated to the one from Listing 2. But it looks so identical to the one of Listing 1 that you could easily imagine an abstraction of the ORM layer. That's what I did by creating DbFinder, a class helper that instanciates either a sfPropelFinder, or a sfDoctrineFinder, based on the nature of the model requested.
$article = DbFinder::from('Article')->
where('Title', 'like', '%foo')->
leftJoin('Author')->
where('Author.Name', 'John Doe')->
orderBy('CreatedAt', 'desc')->
findOne();
So the code from Listing 4 will work whether the Article class extends Doctrine_Record or BaseObject - in other terms, whether the underlying ORM is Doctrine or Propel. DbFinder provides a unified API for communicating with both ORMs.
What's The Use?
I hear the voices of Doctrine users, shouting something like: "DbFinder is much less powerful than Doctrine_Query, so why should I use it?". The answer is 'You should not'. If your ORM is Doctrine, stick to Doctrine_Query and don't use DbFinder at all. Unless you want to switch to Propel sometime in your project, but that's very unlikely.
I also hear voices saying: "Both Doctrine and Propel are abstraction layers. It doesn't make sense to build a layer to abstract abstraction layers". Those voices say right, and being abstract for the sake of being abstract is useless and too Java-like for us RAD fans.
So why bother to develop a Doctrine adapter for DbFinder if it doesn't make sense and if Doctrine users won't use it? Because it allows to develop ORM agnostic plugins. Imagine a plugin where all database queries are made by way of DbFinder. Since both Propel and Doctrine objects extend generated "Base" objects, and share a common API (getters and setters for columns and related objects), this is possible.
That means that plugins like sfSimpleForumPlugin, sfSimpleCMSPlugin, or sfGuardPlugin could have one single version working in both ORMs. No more code duplication between a Propel and an Doctrine version, no more time lost to backport modifications from one version to the other. And most important: a larger user base for all plugins, since both Doctrine users and Propel users can use them.
As a bonus, sfPropelFinder is already a compatibility layer between Propel 1.2 and Propel 1.3. Since you don't need any custom hydration with sfPropelFinder, and since it manipulates resultsets in a different way whether you are on Propel 1.2 or Propel 1.3, all the code written with sfPropelFinder already works in both versions of the ORM.
Where Do I Download This Great Thing?
Don't be in a rush, the Doctrine adapter for DbFinder is far from being finished yet. Only a small share of the sfPropelFinder features are ported to sfDoctrineFinder. And there is no available plugin using DbFinder for now. But if you write your Propel plugins with DbFinder calls starting from now (which you should, since it's so much easier), there is a good chance that you will need little to no effort to make them work with Doctrine once sfDoctrineFinder is finished.
Also, if you are a Doctrine guru and want to give me a hand to write the Doctrine adapter to DbFinder, please send me an email and we'll arrange something.
Comments(7)