Знакомая вам магия Laravel теперь применяется к соединениям.
Объединения очень полезны во многих отношениях. Если вы здесь, то, скорее всего, знаете о них и используете. Eloquent очень мощный инструмент, но ему не хватает «пути Laravel» при использовании соединений. Этот пакет делает ваши объединения более читабельными, с меньшим количеством кода, и при этом скрывает детали реализации из тех мест, где их не нужно раскрывать.
Мы считаем, что при использовании соединений, которые являются очень мощными функциями Eloquent, не хватает нескольких вещей:
Вы можете прочитать более подробное объяснение проблем, которые решает этот пакет, в этом сообщении блога.
Вы можете установить пакет через композитор:
composer require kirschbaum-development/eloquent-power-joinsДля версий Laravel < 10 используйте версию 3.*. Для версий Laravel < 8 используйте версию 2.*:
composer require kirschbaum-development/eloquent-power-joins:3. * Этот пакет предоставляет несколько функций.
Допустим, у вас есть модель User с отношением hasMany к модели Post . Если вы хотите объединить таблицы, вы обычно пишете что-то вроде:
User:: select ( ' users.* ' )-> join ( ' posts ' , ' posts.user_id ' , ' = ' , ' users.id ' ); Этот пакет предоставляет вам новый метод joinRelationship() , который делает то же самое.
User:: joinRelationship ( ' posts ' ); Оба варианта дают одинаковые результаты. Что касается кода, вы не так много сэкономили, но теперь вы используете отношения между моделями User и Post для объединения таблиц. Это означает, что вы теперь скрываете, как эти отношения работают за кулисами (детали реализации). Вам также не нужно менять код, если изменится тип связи. Теперь у вас есть более читаемый и менее громоздкий код.
Но ситуация становится лучше, когда вам нужно присоединиться к вложенным отношениям . Предположим, у вас также есть связь hasMany между моделями Post и Comment и вам нужно соединить эти таблицы, вы можете просто написать:
User:: joinRelationship ( ' posts.comments ' ); Гораздо лучше, согласитесь?! Вы также можете присоединиться к отношениям left или right по мере необходимости.
User:: leftJoinRelationship ( ' posts.comments ' );
User:: rightJoinRelationship ( ' posts.comments ' ); Предположим, у вас есть модель Image , которая представляет собой полиморфную связь ( Post -> morphMany -> Image ). Помимо обычного соединения, вам также потребуется применить where imageable_type = Post::class , иначе вы можете получить неверные результаты.
Оказывается, если вы присоединяетесь к полиморфному отношению, Eloquent Power Joins автоматически применяет это условие для вас. Вам просто нужно вызвать тот же метод.
Post:: joinRelationship ( ' images ' );Вы также можете присоединиться к отношениям MorphTo.
Image:: joinRelationship ( ' imageable ' , morphable: Post::class);Примечание. Запрос преобразования отношений поддерживает только один изменяемый тип одновременно.
Применение условий и обратных вызовов к соединениям
Теперь предположим, что вы хотите применить условие к создаваемому соединению. Вам просто нужно передать обратный вызов в качестве второго параметра методу joinRelationship .
User:: joinRelationship ( ' posts ' , fn ( $ join ) => $ join -> where ( ' posts.approved ' , true ))-> toSql ();Вы также можете указать тип соединения, которое хотите выполнить, в обратном вызове:
User:: joinRelationship ( ' posts ' , fn ( $ join ) => $ join -> left ());Для вложенных вызовов вам просто нужно передать массив, ссылающийся на имена отношений.
User:: joinRelationship ( ' posts.comments ' , [
' posts ' => fn ( $ join ) => $ join -> where ( ' posts.published ' , true ),
' comments ' => fn ( $ join ) => $ join -> where ( ' comments.approved ' , true ),
]);Для принадлежности ко многим вызовам необходимо передать массив со связью, а затем массив с именами таблиц.
User:: joinRelationship ( ' groups ' , [
' groups ' => [
' groups ' => function ( $ join ) {
// ...
},
// group_members is the intermediary table here
' group_members ' => fn ( $ join ) => $ join -> where ( ' group_members.active ' , true ),
]
]); Мы считаем это одной из самых полезных функций данного пакета. Допустим, у вас есть published область видимости вашей модели Post :
public function scopePublished ( $ query )
{
$ query -> where ( ' published ' , true );
}При соединении отношений вы можете использовать области, определенные в объединяемой модели. Насколько это круто?
User:: joinRelationship ( ' posts ' , function ( $ join ) {
// the $join instance here can access any of the scopes defined in Post
$ join -> published ();
}); При использовании областей модели внутри предложения соединения вы не можете ввести подсказку для параметра $query в своей области. Кроме того, имейте в виду, что вы находитесь внутри соединения, поэтому вы можете использовать только условия, поддерживаемые соединениями.
Иногда вам придется использовать псевдонимы таблиц при объединениях, поскольку вы присоединяетесь к одной и той же таблице более одного раза. Один из вариантов добиться этого — использовать метод joinRelationshipUsingAlias .
Post:: joinRelationshipUsingAlias ( ' category.parent ' )-> get ();Если вам необходимо указать имя псевдонима, который будет использоваться, вы можете сделать это двумя разными способами:
Post:: joinRelationshipUsingAlias ( ' category ' , ' category_alias ' )-> get ();as внутри обратного вызова соединения. Post:: joinRelationship ( ' category.parent ' , [
' category ' => fn ( $ join ) => $ join -> as ( ' category_alias ' ),
' parent ' => fn ( $ join ) => $ join -> as ( ' category_parent ' ),
])-> get ()Для сквозных вызовов «принадлежит многим» или «имеет много» вам необходимо передать массив с отношением, а затем массив с именами таблиц.
Group:: joinRelationship ( ' posts.user ' , [
' posts ' => [
' posts ' => fn ( $ join ) => $ join -> as ( ' posts_alias ' ),
' post_groups ' => fn ( $ join ) => $ join -> as ( ' post_groups_alias ' ),
],
])-> toSql (); При создании объединений использование select * from ... может быть опасным, поскольку поля с одинаковыми именами в родительской и объединенной таблицах могут конфликтовать. Подумайте об этом: если вы вызываете метод joinRelationship без предварительного выбора каких-либо конкретных столбцов, Eloquent Power Joins автоматически включит это для вас. Например, взгляните на следующие примеры:
User:: joinRelationship ( ' posts ' )-> toSql ();
// select users.* from users inner join posts on posts.user_id = users.idИ, если вы укажете оператор выбора:
User:: select ( ' users.id ' )-> joinRelationship ( ' posts ' )-> toSql ();
// select users.id from users inner join posts on posts.user_id = users.id При объединении любых моделей, использующих признак SoftDeletes , ко всем вашим объединениям также будет автоматически применяться следующее условие:
and " users " . " deleted_at " is null Если вы хотите включить удаленные модели, вы можете вызвать метод ->withTrashed() в обратном вызове соединения.
UserProfile:: joinRelationship ( ' users ' , fn ( $ join ) => $ join -> withTrashed ()); Вы также можете вызвать модель onlyTrashed :
UserProfile:: joinRelationship ( ' users ' , ( $ join ) => $ join -> onlyTrashed ());Если в определениях отношений есть дополнительные условия, они будут автоматически применены к вам.
class User extends Model
{
public function publishedPosts ()
{
return $ this -> hasMany (Post::class)-> published ();
}
} Если вы вызовете User::joinRelationship('publishedPosts')->get() , он также применит дополнительную опубликованную область к предложению соединения. Это выдаст примерно такой SQL:
select users. * from users inner join posts on posts . user_id = posts . id and posts . published = 1 Если к вашей модели применены глобальные области, вы можете включить глобальные области, вызвав метод withGlobalScopes в предложении соединения, например:
UserProfile:: joinRelationship ( ' users ' , fn ( $ join ) => $ join -> withGlobalScopes ()); Однако здесь есть одна загвоздка. Ваша глобальная область видимости не может указывать класс EloquentBuilder в первом параметре метода apply , иначе вы получите ошибки.
Запрос существования отношений — очень мощная и удобная функция Eloquent. Однако он использует синтаксис where exists , который не всегда является лучшим и не может быть более производительным, в зависимости от количества имеющихся у вас записей или структуры ваших таблиц.
Эти пакеты реализуют ту же функциональность, но вместо использования синтаксиса where exists » используются соединения . Ниже вы можете увидеть методы, реализуемые в этом пакете, а также их эквивалент в Laravel.
Обратите внимание: хотя методы схожи, вы не всегда получите одинаковые результаты при использовании соединений, в зависимости от контекста вашего запроса. Вы должны знать о различиях между запросом данных с помощью where exists и joins .
Собственные методы Laravel
User:: has ( ' posts ' );
User:: has ( ' posts.comments ' );
User:: has ( ' posts ' , ' > ' , 3 );
User:: whereHas ( ' posts ' , fn ( $ query ) => $ query -> where ( ' posts.published ' , true ));
User::whereHas( ' posts.comments ' , [ ' posts ' => fn ( $ query ) => $ query -> where ( ' posts.published ' , true ));
User:: doesntHave ( ' posts ' );Эквивалент пакета, но с использованием соединений
User:: powerJoinHas ( ' posts ' );
User:: powerJoinHas ( ' posts.comments ' );
User:: powerJoinHas ( ' posts.comments ' , ' > ' , 3 );
User:: powerJoinWhereHas ( ' posts ' , function ( $ join ) {
$ join -> where ( ' posts.published ' , true );
});
User:: powerJoinDoesntHave ( ' posts ' ); При использовании метода powerJoinWhereHas со связями, включающими более одной таблицы (один ко многим, многие ко многим и т. д.), используйте синтаксис массива для передачи обратного вызова:
User:: powerJoinWhereHas ( ' commentsThroughPosts ' , [
' comments ' => fn ( $ query ) => $ query -> where ( ' body ' , ' a ' )
])-> get ()); Вы также можете отсортировать результаты запроса, используя столбец из другой таблицы, используя метод orderByPowerJoins .
User:: orderByPowerJoins ( ' profile.city ' );Если вам нужно передать некоторые необработанные значения для порядка по функции, вы можете сделать это следующим образом:
User:: orderByPowerJoins ([ ' profile ' , DB :: raw ( ' concat(city, ", ", state) ' ]); Этот запрос отсортирует результаты на основе столбца city в таблице user_profiles . Вы также можете отсортировать результаты по агрегатам ( COUNT , SUM , AVG , MIN или MAX ).
Например, чтобы отсортировать пользователей с наибольшим количеством сообщений, вы можете сделать это:
$ users = User:: orderByPowerJoinsCount ( ' posts.id ' , ' desc ' )-> get ();Или получить список публикаций, комментарии к которым содержат наибольшее среднее количество голосов.
$ posts = Post:: orderByPowerJoinsAvg ( ' comments.votes ' , ' desc ' )-> get (); У вас также есть методы для SUM , MIN и MAX :
Post:: orderByPowerJoinsSum ( ' comments.votes ' );
Post:: orderByPowerJoinsMin ( ' comments.votes ' );
Post:: orderByPowerJoinsMax ( ' comments.votes ' );Если вы хотите использовать левые соединения при сортировке, вы также можете:
Post:: orderByLeftPowerJoinsCount ( ' comments.votes ' );
Post:: orderByLeftPowerJoinsAvg ( ' comments.votes ' );
Post:: orderByLeftPowerJoinsSum ( ' comments.votes ' );
Post:: orderByLeftPowerJoinsMin ( ' comments.votes ' );
Post:: orderByLeftPowerJoinsMax ( ' comments.votes ' );Пожалуйста, смотрите ВКЛАД для получения подробной информации.
Если вы обнаружите какие-либо проблемы, связанные с безопасностью, отправьте электронное письмо по адресу [email protected] вместо использования системы отслеживания проблем.
Разработка этого пакета спонсируется Kirschbaum Development Group, компанией, ориентированной на разработчиков, ориентированной на решение проблем, создание команды и сообщество. Узнайте больше о нас или присоединяйтесь к нам!
Лицензия MIT (MIT). Дополнительную информацию см. в файле лицензии.