Знакомая вам магия 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). Дополнительную информацию см. в файле лицензии.