I think that these tools are a matter of "Your Mileage May Vary". I am pretty happy using Django's ORM at work because I like the abstraction that it provides, though in some cases the law of leaky abstraction rears its ugly head and I have to delve into SQL. Consider that "nightmarish" example:
SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC;
I suspect that "SELECT u" should be "SELECT *". Anyway, let's proceed to look at their example code:
$qb->add('select', $qb->expr()->select('u'))
->add('from', $qb->expr()->from('User', 'u'))
->add('where', $qb->expr()->orx(
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC'));
Can you spot the bug? Yes, the 'ASC' should be 'DESC'. Now, in Django's ORM, given id and nickname variables, I might write this line of Python:
User.objects.filter(Q(id=id) | Q(nickname__contains=nickname)).order_by('-surname')
The '-' in '-surname' concisely indicates to order in reverse, but it could well be more difficult to spot than 'ASC' versus 'DESC'. Yet the conciseness is somewhat of an illusion as the code is comparable to the SQL. What's more fun for me is to work with a queryset of User objects, i.e., to think at a higher level of abstraction than rows in the database.