0

I have my User linked to Event entity. Basically, users may wanna go to several events and, through a many to many, I'm registering who want to go to X event.

I need to build a Query with Doctrine that pick up users if they are available (available set to 1) OR if they plan on going to an event depending on their location ( I got this one too ). The first conditions is fine. But for the second one, I need to make sure that the user has at least one event he wants to go to, registered in the many to many relation: eventNotified.

In Php I would do a foreach loop and then condition if the $user->getEventNotified() is not an empty array ( or null ) but that would make me pull up too many results. I want to select it with SQL or DQL

How can I do that?

Here is what I have achieved so far, I just need to add a OR condition if the user wants to go an event (is eventNotified not null ?):

//We only want to return users nearby who are available OR who 
    $qb = $this->_em->getRepository( 'Entity\User' )->createQueryBuilder('a');

    $qb->andwhere( 'a.latitude > :minLat' );
    $qb->andWhere( 'a.latitude < :maxLat' );
    $qb->andWhere( 'a.longitude > :minLng' );
    $qb->andWhere( 'a.longitude < :maxLng' );
    $qb->andWhere( 'a.available = 1' );

    $qb->add( 'orderBy', $qb->expr()->sum( 'a.latitude - :lat', 'a.longitude - :lng' ));

    $array = array(
         'minLat' => $minLat,
         'maxLat' => $maxLat,
         'minLng' => $minLng,
         'maxLng' => $maxLng,
         'lat'    => $lat,
         'lng'    => $lng
    );

    $qb->setParameters( $array );

    // $qb->setFirstResult( $offset );
 //    $qb->setMaxResults( $limit );

    // print_r(array(
    //     'sql'        => $qb->getQuery()->getSQL(),
    //     'parameters' => $qb->getQuery()->getParameters(),
    // ));
    $usersNearby = $qb->getQuery()->getResult();

    return $usersNearbt;

And here is my user entity:

class User
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false, unique=true)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var integer
     *
     * @ORM\Column(name="fb_id", type="bigint", nullable=false, unique=true)
     */
    private $fb_id;

    /**
     * @var string
     *
     * @ORM\Column(name="firstname", type="string", length=100, nullable=false, unique=false)
     */
    private $first_name;

    /**
     * @var string
     *
     * @ORM\Column(name="lastname", type="string", length=100, nullable=true, unique=false)
     */
    private $last_name;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255, nullable=true, unique=true)
     */
    private $email;

    /**
     * @var integer
     *
     * @ORM\Column(name="notation", type="integer", nullable=true, unique=true)
     */
    private $notation;

    /**
     * Bidirectional - Many users have Many favorite comments (OWNING SIDE)
     *
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="Entity\Category", inversedBy="userInterests")
     */
    private $interests;

    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="Entity\User", cascade={"persist"})
     * @ORM\JoinTable(name="friends",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="friend_user_id", referencedColumnName="id")}
     *      )
     **/
    private $friends;

    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\OneToMany(targetEntity="Entity\Request", mappedBy="user", cascade={"remove"}, orphanRemoval=true)
     * @ORM\JoinColumn(nullable=true)
     */
    private $requests;

    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\OneToMany(targetEntity="Entity\Request", mappedBy="friend", cascade={"remove"}, orphanRemoval=true)
     * @ORM\JoinColumn(nullable=true)
     */
    private $notifications;

    /**
     * Bidirectional - Many users have notified they want to go to different events (OWNING SIDE)
     *
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="Entity\Event", inversedBy="userNotified", cascade={"persist"})
     */
    private $eventNotified;

    /**
     * @var integer
     *
     * @ORM\Column(name="age", type="integer", length=3, nullable=true, unique=false)
     */
    private $age;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="text", nullable=true, unique=false)
     */
    private $description;

    /**
     * @var string
     *
     * @ORM\Column(name="picture", type="string", length=300, nullable=true, unique=false)
     */
    private $picture;

    /**
     * @var string
     *
     * @ORM\Column(name="genre", type="string", length=10, nullable=true, unique=false)
     */
    private $genre;

    /**
     * @var boolean
     *
     * @ORM\Column(name="isregistered", type="boolean", length=1, nullable=false, unique=false)
     */
    private $registered;

    /**
     * @var string
     *
     * @ORM\Column(name="latitude", type="decimal", length=64, precision=25, scale=20, nullable=true, unique=false)
     */
    private $latitude;

    /**
     * @var string
     *
     * @ORM\Column(name="longitude", type="decimal", length=64, precision=25, scale=20, nullable=true, unique=false)
     */
    private $longitude;

    /**
     * @var \Entity\Security_Key
     *
     * @ORM\OneToOne(targetEntity="Entity\Security_Key", cascade={"persist","remove"}, orphanRemoval=true)
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="private_key_id", referencedColumnName="id", unique=true, onDelete="SET NULL")
     * })
     */
    private $private_key;

    /**
     * @var boolean
     *
     * @ORM\Column(name="isavailable", type="boolean", length=1, nullable=false, unique=false)
     */
    private $available = 0;
...

EDIT:

Thanks to the solution below I had it working with a little tweak ( and a few optimisations ):

    $qb = $this->getEntityManager()->createQueryBuilder();

    $qb->select( 'USER', 'EVENT', 'c', 'p' )
        ->from( 'Entity\User',  'USER' )
        ->innerJoin( 'USER.eventNotified', 'EVENT' )
        ->leftJoin( 'EVENT.categories', 'c' )
        ->leftJoin( 'EVENT.place', 'p' )
        ->add('where', $qb->expr()->andX(
                $qb->expr()->between('USER.latitude', ':minLat', ':maxLat'),
                $qb->expr()->between('USER.longitude', ':minLng', ':maxLng')
            )
        )
        ->add( 'orderBy', $qb->expr()->sum( 'USER.latitude - :lat', 'USER.longitude - :lng' ) );

    $array = array(
         'minLat' => $minLat,
         'maxLat' => $maxLat,
         'minLng' => $minLng,
         'maxLng' => $maxLng,
         'lat'    => $lat,
         'lng'    => $lng
    );

    $qb->setParameters( $array );

    $usersNearby = $qb->getQuery()->getResult();
Miles M.
  • 4,089
  • 12
  • 62
  • 108

1 Answers1

1

You need to join to the ManyToMany entity. If the user hasn't got any Event related (by your User.eventNotified property), he/she won't be retrieved. I also would build the where conditions using the Expr class ofered by QueryBuilder:

$qb = $this->getEntityManager()->createQueryBuilder();

$qb->select( 'USER' )
    ->from( 'Entity\User',  'USER' )
    ->innerJoin( 'USER.eventNotified' )
    ->where( 
        $qb->expr()->andX(
            $qb->expr()->gt( 'USER.latitude', ':minLat' ),
            $qb->expr()->lt( 'USER.latitude', ':maxLat' ),
            $qb->expr()->gt( 'USER.longitude', ':minLng' ),
            $qb->expr()->lt( 'USER.longitude', ':maxLng' ),
            $qb->expr()->eq( 'USER.available', 1 )
        )
    ); 

(...) //orderBy and parameters

Check the QueryBuilder documentation.

lluisaznar
  • 2,383
  • 1
  • 15
  • 14
  • Thanks ! When looking at the User entity, you can see its a manytomany self referencing. A user has friends who are users themselves. How do I make this exact same search in within a user's friends? – Miles M. Feb 03 '14 at 03:56
  • 1
    It seems my solution answers this question. You've posted a totally different [question](http://stackoverflow.com/questions/21519480/how-to-look-for-an-entitys-self-referenced-entity-in-my-doctrine-search) copy&pasting it. Why don't you accept it? By the way, do a **leftJoin** with the **friends** property (in order to not lose the users which have no friends but match the **where** clause). After that do an **innerJoin** with the left joined entity. – lluisaznar Feb 03 '14 at 05:55
  • Thanks man I'll try that. I was still testing your solution and solving this new one .., but I didn't really understand your second solution ( I don't need to keep the users who are not friends, I'm trying to limit what is returned from Doctrine as much as possible (for performances, don't want to pull to many unecessary data)). I'm not very good at joining table. Shouldn't it be something like "select the users, friends to this user who are meeting these conditions" Could you give me an example on the other one please? Many thanks – Miles M. Feb 03 '14 at 10:02
  • Actually, i can't have your solution working, I get a `Fatal error: Uncaught exception 'Doctrine\ORM\Query\QueryException' with message 'SELECT USER, * FROM Entity\User USER WHERE USER.latitude > :minLat AND USER.latitude < :maxLat AND USER.longitude > :minLng AND USER.longitude < :maxLng AND USER.available = 1 ORDER BY a.latitude - :lat + a.longitude - :lng' in /Sites....` – Miles M. Feb 04 '14 at 05:51
  • I've updated the query, there was an error in the **select** function, an unneeded '' parameter. Regarding my query this could be the problem, you should take a look into this [question](http://stackoverflow.com/questions/15257538/how-to-order-by-a-computed-value-in-dql) about your computed orderBy expression. – lluisaznar Feb 04 '14 at 06:43
  • I'm stil trying to have it working, I'm very close thank you, I'll update my post whenever it works – Miles M. Feb 04 '14 at 19:28
  • The inner join seems to be the problem actually – Miles M. Feb 04 '14 at 19:30
  • Grab the real SQL statement executed. You can get it at class **Doctrine\DBAL\Connection** function **executeQuery**. Let's see if the query is ok. Maybe the problem is in the definition of **Event** entity. You could improve your question including all code from **User** and **Event** entities. – lluisaznar Feb 05 '14 at 06:47
  • sure, I'm still doing tests – Miles M. Feb 06 '14 at 05:05