Laravel: Eloquent упорядочивание по hasMany отношениям

Источник: «Eloquent Order by HasMany Relationship: Three Ways»
Представьте, что вы хотите загрузить Модель со многими связанными моделями, но отсортировать эти связанные результаты по некоторому столбцу в этой связанной БД. Как это сделать? Давайте сделаем ещё веселее и возьмём пример двухуровневых отношений.

Сценарий: у вас есть User -> hasMany Posts -> hasMany Comments, и вы хотите загрузить пользователей с их сообщениями и комментариями, но комментарии упорядочены по наибольшему количеству голосов, поля comments.likes_count БД.

Структура модели:

app/Models/User.php:

class User extends Authenticatable
{
public function posts()
{
return $this->hasMany(Post::class);
}
}

app/Models/Post.php:

class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}

app/Http/Controllers/Api/UserController.php:

class UserController extends Controller
{
public function index()
{
return User::with('posts.comments')->get();
}
}

Давайте заполним базу данных одним пользователем, одним сообщением и двумя комментариями:

Результат в формате JSON:

[
{
"id": 1,
"name": "Prof. Tito Macejkovic MD",
"email": "charlene.olson@example.org",
"posts": [
{
"title": "Post 1",
"comments": [
{
"id": 1,
"comment_text": "First comment",
"likes_count": 1,
"created_at": "2023-01-11T19:11:45.000000Z",
},
{
"id": 2,
"comment_text": "Second comment",
"likes_count": 3,
"created_at": "2023-01-11T19:11:45.000000Z",
}
]
}
]
}
]

Теперь, как упорядочить по comments.likes_count вместо упорядочивания по умолчанию по comments.id?

Три способа.

Вариант 1. Функция обратного вызова с orderBy

При загрузке отношения — неважно, один или два уровня — вы можете поместить его в массив ключ-значение, где ключ — имя отношения, а значение — функция обратного вызова с дополнительными условиями, такими как orderBy или какие вы захотите.

class UserController extends Controller
{
public function index()
{
return User::with(['posts.comments' => function($query) {
$query->orderBy('comments.likes_count', 'desc');
}])->get();
}
}

Результат:

[
{
"id": 1,
"name": "Prof. Tito Macejkovic MD",
"email": "charlene.olson@example.org",
"posts": [
{
"title": "Post 1",
"comments": [
{
"id": 2,
"comment_text": "Second comment",
"likes_count": 3,
"created_at": "2023-01-11T19:11:45.000000Z",
},
{
"id": 1,
"comment_text": "First comment",
"likes_count": 1,
"created_at": "2023-01-11T19:11:45.000000Z",
}
]
}
]
}
]

Вариант 2. Модель: ВСЕГДА упорядочивайте по полю X

Может быть, вы хотите, чтобы эти отношения всегда упорядочивались по этому полю?

Вы можете сделать так:

app/Models/Post.php:

class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class)->orderBy('likes_count', 'desc');
}
}

Затем, в контроллере вы просто оставляете всё как есть, User::with('posts.comments')->get(), и результаты в любом случае будут упорядочены правильно.

Но если вы хотите сделать исключение и в некоторых случаях упорядочить его по другому, вы можете сделать в Контроллере так:

public function index()
{
return User::with(['posts.comments' => function($query) {
$query->reorder('comments.id', 'desc');
}])->get();
}

Вариант 3. Отделяйте упорядоченные отношения

Если вы захотите использовать это упорядочивание часто, но не всегда, другой вариант — создать отдельную функцию отношений:

app/Models/Post.php:

class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}

public function commentsByMostLikes()
{
return $this->hasMany(Comment::class)->orderBy('likes_count', 'desc');

// Или как вариант, даже...
return $this->comments()->orderBy('likes_count', 'desc');
}
}

app/Http/Controllers/Api/UserController.php:

class UserController extends Controller
{
public function index()
{
return User::with('posts.commentsByMostLikes')->get();
}
}

Дополнительные материалы

Предыдущая Статья

Laravel 10: Что такое Processes / Процессы

Следующая Статья

Laravel: Как передать глобальные переменные в Blade.