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.
Possibly related posts (automatically generated):
looks very promising and will be a nice addition to those ORM layers and also to the symfony framework.
Nice Job!
Great approach, and I see it as a must for symfony, considering that their leaders say that Propel and Doctrine would (need to) be equally supported in future (which I do support). Because we'll end up in a mess if we need a set of variants for each plugin, only to support all ORM libs. Or worse like now, to have a plugin working only with Propel, and another for Doctrine only... Cheers RAPHAEL
Adding support for Doctrine actually turned me to have a closer look at your plugin. In fact I believe this is the BEST news since ages!! I have always wondered how we could have an ORM AL as it is just too ugly to maintain plugins twice.
It is THE DRY solution for our "ORM problem"!
I think it should be considered to bundle this with symfony as soon as it is in a stable state.
One thing I suggest is to rename DbFinder to sfDbFinder just for consistency. Or what about sfORMFinder, or simply sfORM ?
Ok.. just an idea.
MAN!!! Are you a Genius?
I hope have it. Once write a sentence, and think another things!!!
And when , some ORM , will be better than other, just change a line, and it's ready!
It's a great idea
I think this approach is perfect for plugin development. But what about the things you mentioned in one of your previous post, about complex queries, that means that maybe someone will have to use native calls depending the ORM, because sfXXFinder will not be enough for all cases, especially with propel. It would be great if the this abstraction layer give to propel ORM a more DQL facilities.
BTW: Can I post a translation of this post in the Hispanic community of symfony? I tried to contact you with the symfony-project email, but no answer arrived.
This is a great idea.
BTW: Is sfPropelFinder updated for 1.1? I can't seem to get an install for 1.1 through the channel.
Hey, two quick points:
A/ It will add a layer, which means it will slow the stuff down a little bit.
B/ Many plugins can't be made ORM agnostic. I'm talking of behaviours, which are totally different on Propel and Doctrine
But it's still a very nice idea for all those common plugins. It's also a good idea not to have beginners learn many ORMs on top of learning symfony already. A single documentation can be enough, and that's good news.
Good luck and congrats Cedric