Some time ago, I have published the Doctrine 2 video tutorial. It is created from the point of view of a programmer who tried Doctrine for the first time after reading a tutorial and some parts of the documentation. And because I am the author of NotORM library which can solve the same task as Doctrine, the same tutorial is also created using my library.
The result of this tutorial is that I am not satisfied with Doctrine 2 and I can hardly imagine using it in my projects. Benjamin Eberlei, the Lead Developer of Doctrine found his time to answer my remarks and he has agreed with publishing the conversation.
Benjamin: Only the usage of the
Tools depend on
Symfony\Component\Console, but that is shipped with the download. You can use the tools without the
Symfony\Component\Console dependency. You show in the tutorial how to circumvent the console Symfony dependency.
Jakub: I know. It just seems a little bit weird to me if (a part of) some framework is bundled with Doctrine and some parts of Doctrine really depends on that framework.
Symfony\Component is a component framework. So you can ship your projects with only that parts.
Benjamin: Proxies are almost always used, normally you just don't realize it. We will however add a better default here using evil
eval. Its not really a recommendation, its required to have properties private/protected (for lazy loading). Use
__set to save LOC at that point (IDEs generate getter/setter for you in no time).
Benjamin: Yay the
/* errors are annoying. That is why I personally use XML for metadata mapping.
Benjamin: What version did you use? I have never seen this kind of bug. Did you report it?
Jakub: The bug is still there in current official release, I will report an issue.
Benjamin: Where else would you add them? Behind the property specified by the Order of the Properties in the class? The
updateSchema does an incremental update, not a regeneration. That is why its added at the end. You can recreate the whole thing and have it in order or just use schema tool to generate only the queries for you to execute (and modify them). This is all more work, in the end it doesnt really matter for rapid prototyping where the column is. You can fix it later.
Jakub: The columns should be added at the place where I specify them in the entity. Some database systems allow it (e.g. MySQL by
AFTER clause in
ALTER TABLE). I want to use
updateSchema not only for rapid prototyping but for all changes even in production quality projects. I see
updateSchema as one of the killer features of Doctrine but it is not perfect.
Benjamin: I add a feature request, sounds interesting.
Benjamin: One query per loop means you are using lazy load as you show and can easily optimize it with DQL.
Jakub: Is there a way to efficiently solve this task (print N newest articles with all their tags) in Doctrine 2? I don't see any.
Benjamin: There is the DoctrineExtensions Pagination. In 2.0 there is no other solution. But we plan to add a feature to do this in a two-step procedure. i) Load all articles and categories ii) load all tag collections that are not yet loaded in a single query. This would load all the data in two queries.
Jakub: Pagination extension is not effective. If your future solution would not read all the data but only the required rows then it would be great.
Benjamin: Why is the pagination extension not effective? For the SELECT query getting the tags along side it would only require 3 queries and load only the 10 articles from the frontpage and all their related tags. Yes its transfering more data again (join leading to multiple hydration of the same row), but the query count is still very low. The approach I have in mind works as yours in NotORM. Select all articles, then select ALL tags that are in any of the previously fetched articles using an
IN query and then building the collections from there.
Jakub: Yes, the pagination extension is not effective because it transfers the same data again and again (article text for each tag). The optimal solution is provided by NotORM.
Benjamin: There is an extension that allows you to efficiently paginate joined resultsets additionally we plan to add another feature (besides DQL fetch join) that allows to load several collections at once (eager) if requested, not per loop iteration.
Jakub: The eager feature will be hopefully the solution of this problem. Until that, there's no efficient solution.
Benjamin: That is indeed a problem, but not at all as huge as you describe. Transfering more data is still faster and more efficient than the N+1 problem. But the first usage is only the unoptimized one, since Doctrine 2 is an ORM isn't it nice something like PARTIAL exists at all? I know no other ORM that supports this. You could also do something like:
$em->createQuery("SELECT a.id, a.headline, a.text FROM Article a ORDER BY a.published DESC")->getArrayResult() and just get an array. Very efficient if you need it.
Jakub: The PARTIAL feature can help to solve some issues but not all of them. I still may need to transfer one big data (e.g. article perex) and bunch of related items (e.g. article tags). Besides, I don't like PARTIAL because it requires more work from a programmer. NotORM has a much better feature - it can automatically transfer only those columns which are really used.
Benjamin: Yes thats the difference between an ORM and not an ORM. The ORM only works with full objects or in the case of Doctrine you can use the Array hydrator.
Jakub: I'm not sure if I've explained it well - NotORM has a feature which automatically transfers only the really used columns. So it's like PARTIAL in Doctrine but you don't need to write it by hand.
Benjamin: How does it work? I mean where do you know at line N that you only need columns a,b,c in the following lines N+1?
Jakub: It is described at Limiting selected columns.
Benjamin: M:N in an OO world means reference. A reference can't have additional information in the OO world. You can create a new entity for the M:N table and make it a N:1 1:M relationsship. With Doctrine 2.1 this will even have very powerful composite foreign key support.
Jakub: That's exactly what I'm talking about. One trivial change in the task means significant rewrite of the code using Doctrine. Good solution would require only a trivial modification (as in NotORM).
Benjamin: True you'd have to rewrite a lot. But normally you know up front if its a real many-to-many or a M:1 N:1.
Jakub: I'm not sure if you know it upfront, mainly with some agile form of development. For example you can create a shopping basket as M:N basket:product relation but then you realize that it would be nice to store also the number of products. Or you can store products in several categories - clearly M:N relation but then you realize that maybe some categories would be more important for a product than the others. This is quite common development pattern. With NotORM, these kind of changes are trivial. With Doctrine, it requires a massive rewrite.