Каков правильный способ получить дочерние элементы сущности в Symfony?

Я использую Symfony4. У меня есть структура базы данных с тремя таблицами, с двумя связывающими их отношениями "один ко многим". Например:

Дом -> Человек -> Собака

В каждом доме может быть много людей, у каждого человека может быть много собак. (Моя база данных на самом деле не о домах и собаках, фактические имена таблиц были изменены, чтобы защитить невинных.) Используя сущности Doctrine, у меня, конечно, есть такие функции, как $house->getPersons() и $person->getDogs(). Но как правильно реализовать такую ​​функцию, как $house->getDogs()? Есть несколько решений, которые я могу придумать, но ни одно из них не кажется мне "хорошим" решением:

  • Я мог бы просто добавить отношение «один ко многим», относящееся к Дому и Собаке. Однако это противоречит одному из фундаментальных правил нормальной структуры базы данных: никаких избыточных данных. Отношение дом->человек->собака уже выражено в базе данных, поэтому добавление еще одного отношения непосредственно из дом->собака не только избыточно, но и потенциально может привести к ситуации, когда человек собаки живет в одном доме, а собака живет в другом (чего мне не хочется).

  • Я мог бы просто сделать что-то вроде

    foreach($house->getPersons() as $person){
        $person->getDogs();
        // Do something with each dog
        ...
    }
    

    Это не очень хорошо оптимизировано, так как я буду запускать отдельный запрос для каждого человека - потенциально много запросов - когда я мог бы легко выполнить один запрос с парой объединений.

  • Я мог бы использовать HouseRepository или построитель запросов внутри объекта House для запуска пользовательского запроса. Это, насколько я понимаю, осуждается в Symfony. Обычно мы не хотим никаких DQL или использования репозиториев в классах сущностей, верно?

  • Я мог бы использовать HouseRepository из сервисов/контроллеров для запуска пользовательского запроса. Это был бы простой способ, но он кажется немного неэлегантным. Тем не менее, у меня есть ощущение, что это может быть "правильный" путь.

Подводя итог: у меня есть ощущение, что я должен могу каким-то образом поместить это довольно простое двойное соединение в свою сущность, и мне не нужно переходить на уровень репозитория, чтобы получить эти данные. Чтобы было ясно, я не спрашиваю, как написать соединение DQL, я спрашиваю, где его правильно разместить, или есть ли какой-нибудь умный способ Symfony сделать это с помощью Criteria или чего-то подобного.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
3
0
64
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если вы хотите вызвать $house->getDogs(), вы можете добавить метод в свой объект House.

public function getDogs()
{
    /** @var Doctrine\Common\Collections\ArrayCollection */
    $dogs = new ArrayCollection();

    if ($persons = $this->getPersons()) {
        foreach($persons as $person) {
            if ($d = $person->getDogs()) {
                $dogs->add($d);
            }
        }
    }

    return $dogs;
}

Мне нравится это, потому что это дает мне метод объекта для вызова, но мне это не нравится, потому что, как и в моем решении № 2, оно выполняет отдельный запрос для каждого человека и, таким образом, относительно плохо оптимизировано. Это может быть то, с чем я в конечном итоге пойду, если нет лучшего способа.

kgarthai 19.03.2019 17:33

Я ответил на вопрос "Как реализовать функцию типа $house->getDogs()". Конечно, более оптимизированный запрос. Вы можете создать метод findByHouse(House $house) в своем DogRepository.

acucchieri 19.03.2019 20:46

Повторение каждого человека так, как вы это сделали, совершенно правильно. Если вы хотите свести к минимуму количество обращений к базе данных, вы можете использовать некоторые операторы соединения, чтобы «предварительно выбрать» все, что вам нужно, за одну поездку в базу данных. Используя метод, подобный показанному здесь, попробуйте код ниже.

Предполагая, что отношение «один ко многим собака-> люди» и второе отношение «человек ко многим»-> собаки, тогда в вашем HouseRepository используйте метод, который извлекает все соответствующие данные в одном запросе, включая всех связанных лиц и собак. .

class HouseRepository extends ServiceEntityRepository
{
    $qb = $this->createQueryBuilder('house')
        ->leftJoin('house.persons', 'persons')
        ->addSelect('persons')
        ->leftJoin('persons.dogs', 'dogs')
        ->addSelect('dogs')
        ->getQuery()->getResult();
}

Это, конечно, не проверено, и я подозреваю, что возвращаемая структура данных будет настолько беспорядочной, что даже если она работает, ваш код может быть уродливым для ее обработки.

Другие вопросы по теме