0

A Symfony + Doctrine based project uses a User calls to manage registered users. An additional UserMetadata class is used to handle all kind of additional information (e.g. last login date, has visited page xy, etc.) about a user.

UserMetadata has a unidirectional many-to-one relationship to User (one user can have many linked metadata entries. User class does not know about the metadata).

class UserMetadata {
    ...

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
     */   
    protected $user;

    protected $key;
    protected $value;

A metadata entry is stored as key/value pair. For example, the date a user last visited a certain page is stored as key='somePageName', value='2020-12-01' is added. If no entry with key='somePageName' exists, the user has not visited this page yet.

Goal 1: Create a query which selects all users which have NOT visited a certain page.

Finding the users who HAVE visited a page is no problem using a LEFT JOIN:

$qb = $this->em->createQueryBuilder();
$qb->select('u')
    ->from('AppBundle\Entity\User', 'u')
    ->andWhere('u.someValue = :filter1')
    ->setParameter('filer1', 'someFilterValueInUserEntity')

    ->leftJoin(UserSettingsEntry::class, 's', 'WITH', 's.user=u.id')
    ->andWhere('s.key = :key')
    ->setParameter('key', 'somePageName');

But how to find users WITHOUT such an entry?

Goal 2: Create a query which selects all users which have NOT visited a certain page, OR where the page was visited more than 6 weeks ago.

Is this possible in the one query?

Andrei Herford
  • 17,570
  • 19
  • 91
  • 225

2 Answers2

0

You can use ->notIn() method:

Get your original query results:

$myUsers = $qb->select('u')
    ->from('AppBundle\Entity\User', 'u')
    ->andWhere('u.someValue = :filter1')
    ->setParameter('filer1', 'someFilterValueInUserEntity')

    ->leftJoin(UserSettingsEntry::class, 's', 'WITH', 's.user=u.id')
    ->andWhere('s.key = :key')
    ->setParameter('key', 'somePageName')
    ->getQuery()
    ->getResult();

Then you can use your result to filter a new query:

$qb->select('u')
    ->from('AppBundle\Entity\User', 'u')
    ->where($qb->expr()->notIn('u.id', $myUsers))
    ->getQuery()
    ->getResult();

You can check this question and Doctrine references

Agnohendrix
  • 480
  • 1
  • 7
  • 17
0

Since you are using a left join, you can simply check if the right side table entry is null, make sure to move the s.key = :key condition inside the join condition though.

$qb = $this->em->createQueryBuilder();
$qb
    ->select('u')
    ->from('AppBundle\Entity\User', 'u')
    ->andWhere('u.someValue = :filter1')
    ->setParameter('filer1', 'someFilterValueInUserEntity')
    ->leftJoin(UserSettingsEntry::class, 's', 'WITH', $qb->expr()->andX(
        's.user = u.id',
        's.key = :key'
    ))
    ->setParameter('key', 'allo')
    ->andWhere($qb->expr()->isNull('s.id'))
;
Julien B.
  • 3,023
  • 2
  • 18
  • 33