mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Update docs about QueryBuilder? #6130
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @javiereguiluz on GitHub (Dec 12, 2018).
Originally assigned to: @lcobucci on GitHub.
Bug Report
Summary
In this Reddit comment, @Ocramius recommends using DQL with NOWDOC strings instead of QueryBuilder. In fact, he says that "query builder is less safe and less readable"
Current behavior
The main article of QueryBuilder in Doctrine docs says things like this:
Maybe some docs need some update to recommend DQL instead? (except when building queries dynamically, when using QueryBuilder will be better)
Thanks!
@ostrolucky commented on GitHub (Dec 12, 2018):
@Ocramius is free to recommend to use what he personally believes is better even if it's different from what doctrine team recommends. Getting all doctrine team members to get on the same side would be tough.
@Ocramius commented on GitHub (Dec 12, 2018):
Overall, I fully endorse DQL strings over query builders where possible. Query builders lead to:
QueryBuilders (any really: be it SQL, DQL or anything similar) have their place with dynamic DSL assembly (which is what their name implies), but using the DSL directly is more straightforward, readable and less error-prone, plus easily introspectible for anything that can understand the DSL itself (PHPStorm in this case, which is already awesome for SQL).@Ocramius commented on GitHub (Dec 12, 2018):
No, I'm not proposing dynamic string concatenation in userland: that's exactly where you draw the line between DSL and DSL builder.
@Majkl578 commented on GitHub (Dec 12, 2018):
FWIW I typically prefer and recommend QueryBuilder as it's easier to build conditional queries with it. And once you accept it for this use case, I prefer using it elsewhere too for consistency. Also using multiline strings is not something I like, neither annoyingly indented NOWDOCs.
With flexible NOWDOC in PHP 7.3 the situation got better though.
@SenseException commented on GitHub (Dec 12, 2018):
Depending on the database, the querybuilder can hide unfavourable joins. MySQL is ignoring the join order and let's its execution plan decide by default, while other databases take the joins as is. But I agree that the "standard way" makes developers choose this way of create queries because it sounds like a suggested practice. It's an evaluative sentence.
@javiereguiluz commented on GitHub (Dec 13, 2018):
I fully agree with @Majkl578. I do that too for the same reasons.
I know that Marco is a trend-setter, not a trend-follower. So maybe his comment was ahead of time. However, I asked on Symfony's Slack and most people agreed with it and do that too. That's why I opened this issue: maybe using DQL-strings instead of QueryBuilders is much more common than we think 🤔
@Ocramius commented on GitHub (Dec 13, 2018):
Simplistic example that matches what I usually see in the wild:
This happens in multiple issue requests on doctrine/doctrine2, and the typical response from @doctrine/doctrinecore is something along the lines of "what are the generated DQL and SQL strings?", or we typically try to assemble the string mentally, or maybe the original issue reporter modified the
QueryBuilderinstance in unexpected and un-reported ways.Sometimes it even becomes much more esoteric, with extremely complex queries that don't need to be dynamic at all, and involve multiple query builders and
->getDQL()on them.Compare the code above with the equivalent:
I expanded it to make it parallel with the above, but you can apply something like the https://www.sqlstyle.guide/ syntax if you prefer so.
Benefits (applies also to SQL):
<<<'NOWDOC'comments usingDQLas delimiterstring, although being squishy, has this huge advantage)QueryBuilderunless doing dynamic DQLQueryBuildersupport for random custom DQL functions because they learn that everything should be done through theQueryBuilder: this is simply a misconceptionThis is a simplistic example, but much more to the point. I see much worse examples in day-by-day consulting gigs, where enormous query builders spawn for no real reason other than "we were told to use the
QueryBuilder".For some simplistic dynamic DQL operations (think
$dql .= 'AND e.' . $fieldName . ' LIKE :' . $fieldName), I'd even argue that string concatenation is also very much viable.In general, if there is no need to make a query dynamic, it shouldn't be dynamic.
@Ocramius commented on GitHub (Dec 13, 2018):
Just found an extreme example in an app I'm working with. It will not make sense, because I had to drop/change some bits due to NDA and such. Also, not
doctrine/orm-based:There is nothing dynamic in the above, yet this is what the average user is doing, and it leads to unnecessary complexity without any added value.
EDIT: imagine being someone that has to verify the above structure against the SQL optimiser. In order to do that, you'd need to write code to collect the generated SQL string, because doing it by hand will be error-prone. Also note how the developers, thinking that the query builder provides SQL escape safety, simply bound parameters directly into the assembled SQL string. Luckily not a security issue in this case, since all data is from the application configuration itself, but otherwise really problematic.
@odan commented on GitHub (Dec 13, 2018):
A single parameter is enough and a query is dynamic. The parameters
$areas, $dg, $product, $timestampare dynamic in the above example. So it's better to use a QueryBuilder again.@Ocramius commented on GitHub (Dec 13, 2018):
@odan those are parameters, they do not affect the generated string structure.
@stof commented on GitHub (Dec 13, 2018):
@odan no, because the DQL must not contain these parameters (otherwise, you open the door to DQL injection attacks). In DQL, you would write it as
ppc.area IN (:areas), and set the parameter separately on the query.@Slamdunk commented on GitHub (Dec 13, 2018):
@Majkl578 in my experience there are two types of conditional queries:
JOINsWHEREsConditional
JOINs are, for me, absolutely a no-go: easy to hide domain logic, easy to mess with, inexperienced users tend to solve issues withDISTINCTorGROUP BY, killing performance and hiding logic even more.Conditional
WHEREs may be somewhere acceptable, and for the exact reason above, I do enforce in my teams to hardcode the switch in the SQL to make it clearer:@odan commented on GitHub (Dec 13, 2018):
But
$prefixwhould change the DQL and could also be a security issue.In practice, requirements will change very often and suddenly you have to convert a complex DQL string into a QueryBuilder. Some people will be too afraid to refactor the query by just performing a quick and dirty string concatenation hack. And then... BOOM!
@Ocramius commented on GitHub (Dec 13, 2018):
@odan I added a note about the security implications earlier in an edit at https://github.com/doctrine/doctrine2/issues/7520#issuecomment-446896394
As @Slamdunk described above, adding a bit of SQL/DQL through a query builder has potentially catastrophic consequences, and shouldn't be done. The query above is a good example of something that has "grown organically" based on added business requirements, and now it is an unreadable pile of code because of that. Designing a separate SQL/DQL string, or editing an existing DSL structure, is perfectly OK.
@tgalopin commented on GitHub (Dec 13, 2018):
Small question (I may be missing something obvious) : how do you use full class names for entities in DQL (like App\Entity\UserAdress)? I mean, without an editor specifically handling it, it seems quite difficult to use: either you don't have any autocompletion (and need to type a long class name by hand each time) or you use concatenation and ::class, which is kind of ugly. Am I missing something?
@Ocramius commented on GitHub (Dec 14, 2018):
@tgalopin I typed the FQCN or used sprintf, but since PHPStorm now supports DQL, I'd always go with typing the full FQCN.
@Slamdunk commented on GitHub (Dec 14, 2018):
@tgalopin also [in my experience] a typical DQL query only needs a FQCN in the
FROM, all others entities are retrieved/joined/whered/selected through relationships defined in the main entity, so I write just one FQCN per DQL.Cheers for PHPStorm with DQL support
@goetas commented on GitHub (Jun 17, 2019):
@Slamdunk
can not be optimized by the sql analyzer as I know.
@Ocramius commented on GitHub (Jun 17, 2019):
Sure, you will always hit limits in the optimiser, but you can also simplify the SQL further.
Using a builder as a trade-off is surely feasible when needed, but otherwise you are adding complexity.
@goetas commented on GitHub (Jun 17, 2019):
@Ocramius Not being able to be use proper database indexes for me is a no-go compared to conventions that MIGHT reduce "possible" bugs
@Ocramius commented on GitHub (Jun 17, 2019):
Yes, and using query builders increases that uncertainty.
@greg0ire commented on GitHub (Oct 17, 2019):
@kuraobi is working on that one :)
@kuraobi commented on GitHub (Oct 18, 2019):
I'm on it. As a first time contributor to Doctrine, especially the documentation, I was a bit confused with how to update/build/test it locally, so I think I will also add a bit about the documentation to CONTRIBUTING.md.
@kuraobi commented on GitHub (Oct 28, 2019):
Actually I need some clarification about a specific point: in case the user does choose to use the
QueryBuilder, the documentation currently states that->expr()should be used instead of strings. Is it still true? Because$qb->expr()->eq('u.id', '?1')looks a lot messier than'u.id = ?1'...@ostrolucky commented on GitHub (Oct 28, 2019):
I don't think so
@Ocramius commented on GitHub (Oct 28, 2019):
@kuraobi using
'u.id = ?1is exactly the same as$qb->expr()->eq('u.id', '?1'). The idea of the expression/query builders is that they are useful with dynamic DQL, such as whenu.idis a parameter coming from somewhere else.If you don't have dynamic DQL, or your expressions are constant, then using a string is viable and preferable.
@lcobucci commented on GitHub (Oct 29, 2019):
Handled by #7880