11

I am learning about Symfony and Doctrine and created a simple site but I am stuck at this step.

I have two tables: users and languages

Users Contains: id, username ...
Languages Contains: user_id, language...

Here is a image of the twoenter image description here

enter image description here

Now I am trying to fetch by language, like: get user who speaks both english and french and the result would return user id 2

In plain PHP i can do inner join with PDO, but I am trying to follow the doctrine syntax and this does not return the correct result

public function getMatchingLanguages ($a, $b) {
  return $this->createQueryBuilder('u')
    ->andWhere('u.language = :val1 AND u.language = :val2')
    ->setParameter('val1', $a)
    ->setParameter('val2', $b)
    ->getQuery()
    ->execute();
}

I call this method in my controllers, and the query is pretty basic since I can not find a documentation how to do the joins as per my example

hidar
  • 5,449
  • 15
  • 46
  • 70
  • I don't think `Doctrine` provides any more fancy way of doing this. – Jovan Perovic Aug 28 '18 at 23:56
  • What do you mean fancy? I am just asking because the code does not work as intended. It does not return the user for the languages – hidar Aug 29 '18 at 00:03
  • Don't take this the wrong way but your really need to look just a tiny bit harder at the docs. Lots of examples. Not to mention sof questions. – Cerad Aug 29 '18 at 00:18
  • I understand, no problem. I actually managed to create a blog site with login system by reading the docs on Symfony, but doctrine is bit harder to wrap my head around. I find it complex for some reason. – hidar Aug 29 '18 at 02:34
  • Looks like you have a classic OneToMany relationship between your entities. There is a lot of documentation for this. Try [here](https://symfony.com/doc/current/doctrine/associations.html) to start. – ehymel Sep 01 '18 at 12:13
  • Can you add the doctrine annotations for the relation between these entities? – Jannes Botis Sep 03 '18 at 21:25

4 Answers4

6

In your User model add next code:

/**
 * @ORM\OneToMany(targetEntity="Language", mappedBy="user", fetch="EXTRA_LAZY")
 */
public $languages;

In your Language model add next code:

/**
 * @ORM\ManyToOne(targetEntity="User", inversedBy="languages")
 * @ORM\JoinColumns({
 *   @ORM\JoinColumn(name="user_id", referencedColumnName="id")
 * })
 */
public $user;

By this way you define simple One-To-Many relation between User and Language, but it is not enough for getting your user that support both languages. You need to make 2 joins of user table and language table. That's how it looks like (if you use controller):

  $user = $this->get('doctrine')
        ->getEntityManager()
        ->createQueryBuilder()
        ->select('u')
        ->from(User::class, 'u')
        ->join('u.languages', 'l_eng', 'WITH', 'l_eng.language = :engCode')
        ->join('u.languages', 'l_fr', 'WITH', 'l_fr.language = :frCode')
        ->setParameters([
            'engCode' => 'english',
            'frCode' => 'french'
        ])
        ->getQuery()->execute();

Or, if you use UserRepository class (most preferable):

public function findAllByLangs()
{
    return $this->createQueryBuilder('u')
        ->join('u.languages', 'l_eng', 'WITH', 'l_eng.lang = :engCode')
        ->join('u.languages', 'l_fr', 'WITH', 'l_fr.lang = :frCode')
        ->setParameters([
            'engCode' => 'english',
            'frCode' => 'french'
        ])
        ->getQuery()->execute();
}

So main trick is join language table with condition of english to filter users, that support english language AND join language table again but with french in "ON" section to filter users who support french language as well.

Igor Skobelev
  • 339
  • 1
  • 6
2

By analyzing your DB tables, I assume that your Entities are like this

// User.php

class User implements UserInterface
{
    /**
     * @ORM\Column(type="guid")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="UUID")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $username;
}

// Language.php

class Language
{

    /**
     * @ORM\Column(type="guid")
     */
    private $userId;

    /**
     * @ORM\Column(type="string", length=30)
     */
    private $language;
}

If you have the same setup (as above Entities), then you can write your query like this in UserRepository.php

public function getUsersForMatchingLanguages ($langOne, $langTwo) {
    return $this->createQueryBuilder('user')
        ->select('user.id, user.username, language.language')
        ->innerJoin(Language::class, 'language', 'WITH', 'language.user_id = user.id')
        ->where('language.language = :langOne AND language.language = :langTwo')
        ->setParameter('langOne ', $langOne )
        ->setParameter('langTwo', $langTwo)
        ->getQuery()
        ->getResult();
}

This will return you array of results.

hanish singla
  • 782
  • 8
  • 21
  • 2
    As a rule, you should not have to "think" an answer is correct. You should pretty much know it. Perhaps by testing before posting. Your join is completely messed up. And even if you get that fixed up, the use of an IN clause shows you did not understand the original question. – Cerad Aug 29 '18 at 14:39
  • I guess it is not as easy as it seems afterall, because I am getting an error with your code. I tried dozen times to understand and fix the issue but there is always an error like this: `[Semantical Error] line 0, col 21 near 'username, language.language': Error: Class App\Entity\Language has no field or association named username` – hidar Aug 30 '18 at 20:46
  • @Cerad You could have simply pasted a comment with the answer and you would have helped me, instead of commenting what is right and wrong – hidar Aug 30 '18 at 20:47
  • @hanish incase you are wander, I went ahead and added `public $username` field to `App\Entity\Languge` (which I am not sure should be done) and it still throws `SQLSTATE[42S22]: Column not found: 1054 Unknown column 'u0_.username' in 'field list'` error – hidar Aug 30 '18 at 20:49
  • I assume you have put my function to Languge repository. Please move it to User repository instead, as you have "username" column in User entity, not in Language. I will also suggest you to Paste your entities (User and Language) as well to give us more insight. You entities and table must be synced (have have same columns). – hanish singla Aug 31 '18 at 05:08
  • Nope sadly, I work on this project after I get home from job and I tried your solution and gave up soon, still no working solution, i'm waiting for new answers – hidar Sep 02 '18 at 16:23
  • @hidar... Interesting. I have added a few more details to my answer. If it still do not work, Can you put your entities (User and Language) in your question, so we can understand your query in a better way? – hanish singla Sep 02 '18 at 16:59
0

Maybe I am not understand question correctly, please correct me if I am wrong, but if you need user(s) that speaks BOTH languages you have an error in SQL logic not in doctrine. You should do smth like:

SELECT * FROM user u JOIN language l ON u.id = l.user_id AND l.language = 'english' JOIN language l2 ON u.id = l2.user_id AND l2.language = 'french' GROUP BY u.id;

If query correct for you I can write DQL interpretation for it.

Igor Skobelev
  • 339
  • 1
  • 6
0

You can:

  • Inner join with the languages you want
  • use aggregate functions to count the joined results(joined languages)
  • group by the user entity
  • filter the results for count(lang) = 2

Code:

use Doctrine\ORM\Query\Expr\Join;

public function getMatchingLanguages ($a, $b) {
    return $this->createQueryBuilder('u')
                ->addSelect('COUNT(a) as HIDDEN total')
                ->innerJoin('u.languages', 'a', Join::WITH, 'a.language = :val1 OR a.language = :val2')
        ->groupBy('u');
                 ->having('total = :total') //or ->having('COUNT(a) = :total')
        ->setParameter('total', 2)
        ->setParameter('val1', $a)
        ->setParameter('val2', $b)
        ->getQuery()
        ->execute();
}

$this->getMatchingLanguages('english', 'french');

This works by inner joining user only with rows with english or french and then using mysql having to "check" if we got 2 rows for each user.

If you want also the Languages entities hydrated to your result, you cannot add it to the querybuilder result since you group by:

->addSelect('a')

you will have to do another query.

Jannes Botis
  • 11,154
  • 3
  • 21
  • 39