Как получить (объединить) две записи из базы данных с помощью doctrine/symfony4

Я изучаю Symfony и Doctrine и создал простой сайт, но я застрял на этом шаге.

У меня две таблицы: users и languages

Пользователи Содержит: id, username...
Языки Содержит: user_id, язык...

Вот изображение двух enter image description here

enter image description here

Теперь я пытаюсь извлечь язык, например: получить пользователя, который говорит как на english и на french и результат будет возвращать идентификатор пользователя 2

В простом PHP я могу сделать внутреннее соединение с PDO, но я стараюсь следовать синтаксису доктрины, и это не возвращает правильный результат

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();
}

Я вызываю этот метод в своих контроллерах, и запрос довольно простой, так как я не могу найти документацию о том, как делать соединения в соответствии с моим примером

Ответы

Ответ 1

В вашей модели пользователя добавьте следующий код:

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

В вашей языковой модели добавьте следующий код:

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

Таким образом вы определяете простые отношения "один-ко-многим" между пользователем и языком, но этого недостаточно для того, чтобы ваш пользователь поддерживал оба языка. Вам нужно сделать 2 соединения таблицы таблицы и языка пользователя. Как это выглядит (если вы используете контроллер):

  $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();

Или, если вы используете класс UserRepository (наиболее предпочтительный):

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();
}

Поэтому основной фокус в том, присоединиться к языковой таблице с условием английского для фильтрации пользователей, которые поддерживают английский язык и снова, но с французами в присоединиться к языковому столу "ON" раздел для фильтрации пользователей, которые поддерживают французский язык, а также.

Ответ 2

Анализируя таблицы БД, я предполагаю, что ваши Сущности подобны этому

//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;
}

Если у вас такая же настройка (как и выше Entities), вы можете написать свой запрос, как это, в 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();
}

Это вернет вам массив результатов.

Ответ 3

Может быть, я не понимаю вопроса правильно, пожалуйста, исправьте меня, если я ошибаюсь, но если вам нужны пользователи, которые говорят на двух языках, у вас есть ошибка в логике SQL, а не в доктрине. Вы должны сделать что-то вроде:

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;

Если запрос правильный для вас, я могу написать для него интерпретацию DQL.

Ответ 4

Вы можете:

  • Внутреннее соединение с языками, которые вы хотите
  • использовать агрегированные функции для подсчета объединенных результатов (объединенные языки)
  • группы субъектом пользователя
  • фильтровать результаты для count (lang) = 2

Код:

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');

Это работает, когда пользователь соединяет пользователя только со строками с английским или французским, а затем с помощью mysql, чтобы "проверить", если у нас есть 2 строки для каждого пользователя.

Если вы хотите, чтобы объекты "Язык" гидратировались по вашему результату, вы не можете добавить его к результату querybuilder, поскольку вы группируете:

-> addSelect ( 'а')

вам нужно будет сделать еще один запрос.