49

I'd like to construct the following SQL using Doctrine's query builder:

select c.*
from customer c
join phone p
on p.customer_id = c.id
and p.phone = :phone
where c.username = :username

First I tried

$qb->select('c')
    ->innerJoin('c.phones', 'p', Join::ON, $qb->expr()->andx(
        $qb->expr()->eq('p.customerId', 'c.id'),
        $qb->expr()->eq('p.phone', ':phone')
    ))
    ->where('c.username = :username');

But I'm getting the following error

Error: expected end of string, got 'ON'

Then I tried

$qb->select('c')
    ->innerJoin('c.phones', 'p')
    ->where('c.username = :username')
    ->andWhere('p.phone = :phone');

which seems to be working. However, does anyone know what's wrong with the first attempt? I'd like to make the first one work since it resembles more closely to how SQL is structured.

Note: I know we can also write native mysql or dql with Doctrine, but I'd prefer query builder.

EDIT: Below is the entire code

namespace Cyan\CustomerBundle\Repository;

use Cyan\CustomerBundle\Entity\Customer;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;

class CustomerRepository extends EntityRepository
{
    public function findCustomerByPhone($username, $phone)
    {
        $qb = $this->createQueryBuilder('c');

        $qb->select('c')
            ->innerJoin('c.phones', 'p', Join::ON, $qb->expr()->andx(
                $qb->expr()->eq('p.customerId', 'c.id'),
                $qb->expr()->eq('p.phone', ':phone')
            ))
            ->where('c.username = :username');

//        $qb->select('c')
//            ->innerJoin('c.phones', 'p')
//            ->where('c.username = :username')
//            ->andWhere('p.phone = :phone');

        $qb->setParameters(array(
            'username' => $username,
            'phone' => $phone->getPhone(),
        ));

        $query = $qb->getQuery();
        return $query->getResult();
    }
}
starball
  • 20,030
  • 7
  • 43
  • 238
Mr. 14
  • 9,228
  • 6
  • 37
  • 54

2 Answers2

104

I'm going to answer my own question.

  1. innerJoin should use the keyword "WITH" instead of "ON" (Doctrine's documentation [13.2.6. Helper methods] is inaccurate; [13.2.5. The Expr class] is correct)
  2. no need to link foreign keys in join condition as they're already specified in the entity mapping.

Therefore, the following works for me

$qb->select('c')
    ->innerJoin('c.phones', 'p', 'WITH', 'p.phone = :phone')
    ->where('c.username = :username')
    ->setParameter('phone', $phone)
    ->setParameter('username', $username);

or

$qb->select('c')
    ->innerJoin('c.phones', 'p', Join::WITH, $qb->expr()->eq('p.phone', ':phone'))
    ->where('c.username = :username')
    ->setParameter('phone', $phone)
    ->setParameter('username', $username);;
Ege Kaan Gürkan
  • 2,923
  • 2
  • 13
  • 24
Mr. 14
  • 9,228
  • 6
  • 37
  • 54
  • 5
    Can you complete it with the setParameters() for both :phone and :username? I have a ```Invalid parameter number: number of bound variables does not match number of tokens``` – lrkwz Nov 09 '15 at 14:01
  • 3
    What conditions do you put in a WITH vs a WHERE ? – Greg Bell Mar 08 '17 at 00:47
13

You can explicitly have a join like this:

$qb->innerJoin('c.phones', 'p', Join::ON, 'c.id = p.customerId');

But you need to use the namespace of the class Join from doctrine:

use Doctrine\ORM\Query\Expr\Join;

Or if you prefere like that:

$qb->innerJoin('c.phones', 'p', Doctrine\ORM\Query\Expr\Join::ON, 'c.id = p.customerId');

Otherwise, Join class won't be detected and your script will crash...

Here the constructor of the innerJoin method:

public function innerJoin($join, $alias, $conditionType = null, $condition = null);

You can find other possibilities (not just join "ON", but also "WITH", etc...) here: http://docs.doctrine-project.org/en/2.0.x/reference/query-builder.html#the-expr-class

EDIT

Think it should be:

$qb->select('c')
    ->innerJoin('c.phones', 'p', Join::ON, 'c.id = p.customerId')
    ->where('c.username = :username')
    ->andWhere('p.phone = :phone');

    $qb->setParameters(array(
        'username' => $username,
        'phone' => $phone->getPhone(),
    ));

Otherwise I think you are performing a mix of ON and WITH, perhaps the problem.

Sybio
  • 8,565
  • 3
  • 44
  • 53
  • Thanks for pointing that out. But I did have the namespace included. I even tried using the string 'ON' directly. – Mr. 14 Mar 13 '13 at 08:53
  • Can you show us the full query you write (from the start to the end) ? – Sybio Mar 13 '13 at 09:08
  • thanks for taking the time to answer my question. I think I got it working. If you're interested, see my answer. – Mr. 14 Mar 13 '13 at 10:29
  • 9
    +1 for "use Doctrine\ORM\Query\Expr\Join;". For Symfony 2.8 users, we need to use WITH not ON. On is not supported. – Strabek Jun 29 '16 at 08:57