Я использую 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 или чего-то подобного.




Если вы хотите вызвать $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;
}
Я ответил на вопрос "Как реализовать функцию типа $house->getDogs()". Конечно, более оптимизированный запрос. Вы можете создать метод findByHouse(House $house) в своем DogRepository.
Повторение каждого человека так, как вы это сделали, совершенно правильно. Если вы хотите свести к минимуму количество обращений к базе данных, вы можете использовать некоторые операторы соединения, чтобы «предварительно выбрать» все, что вам нужно, за одну поездку в базу данных. Используя метод, подобный показанному здесь, попробуйте код ниже.
Предполагая, что отношение «один ко многим собака-> люди» и второе отношение «человек ко многим»-> собаки, тогда в вашем HouseRepository используйте метод, который извлекает все соответствующие данные в одном запросе, включая всех связанных лиц и собак. .
class HouseRepository extends ServiceEntityRepository
{
$qb = $this->createQueryBuilder('house')
->leftJoin('house.persons', 'persons')
->addSelect('persons')
->leftJoin('persons.dogs', 'dogs')
->addSelect('dogs')
->getQuery()->getResult();
}
Это, конечно, не проверено, и я подозреваю, что возвращаемая структура данных будет настолько беспорядочной, что даже если она работает, ваш код может быть уродливым для ее обработки.
Мне нравится это, потому что это дает мне метод объекта для вызова, но мне это не нравится, потому что, как и в моем решении № 2, оно выполняет отдельный запрос для каждого человека и, таким образом, относительно плохо оптимизировано. Это может быть то, с чем я в конечном итоге пойду, если нет лучшего способа.