Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Laravel: зв'язки Eloquent і N+1

20 питань · ~20 хв · Версія v3.0

Увійдіть, щоб продовжити

Усі типи зв'язків, поліморфні й pivot, запити за наявністю, жадібне завантаження та N+1, збереження пов'язаних моделей - питання всіх рівнів, від junior до senior.

За спробу
20
У пулі
46
Проходжень
0
Середній бал
-
Пройшли на 70%+
-

Питання для підготовки

10 питань

N+1 виникає, коли ви завантажуєте N моделей одним запитом, а потім у циклі звертаєтесь до їхнього зв'язку - це генерує ще N запитів.

$posts = Post::all(); // 1 запит
foreach ($posts as $post) {
    echo $post->author->name; // +1 запит на кожен пост → N запитів
}

Рішення - eager loading через with():

$posts = Post::with('author')->get(); // лише 2 запити загалом

Вкладені та умовні зв'язки:

Post::with(['author', 'comments.user'])->get();        // вкладений eager
Post::with(['comments' => fn ($q) => $q->latest()])->get(); // умовний

Лічильники без завантаження зв'язку - withCount() (без N+1 і без вантаження самих рядків):

$posts = Post::withCount('comments')->get(); // доступ через $post->comments_count

Виявлення: Model::preventLazyLoading(! app()->isProduction()) у boot() кидає виняток на ледачих завантаженнях поза продакшеном; також допомагають Telescope і Debugbar.

Докладніше в документації: Eloquent: Eager Loading

Eloquent підтримує всі поширені типи зв'язків між таблицями, кожен оголошується методом на моделі:

  • One To One - hasOne / belongsTo. Приклад: User ↔ Profile.
  • One To Many - hasMany / belongsTo. Приклад: Post → багато Comment.
  • Many To Many - belongsToMany через проміжну (pivot) таблицю. Приклад: User ↔ Role.
  • Has One/Many Through - доступ до віддаленого зв'язку через проміжну модель.
  • Polymorphic - morphTo / morphMany: модель належить кільком типам (наприклад, Comment може належати і Post, і Video).

Оголошення зв'язку:

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

class Comment extends Model
{
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

Використання:

$post->comments;               // колекція коментарів
$comment->post->title;         // зворотний бік
Post::with('comments')->get(); // eager loading проти N+1

Завжди завантажуйте потрібні зв'язки через with(), щоб уникнути проблеми N+1.

Докладніше в документації: Зв’язки Eloquent

Поліморфний зв'язок дозволяє моделі належати кільком різним типам моделей через один зв'язок.

class Comment extends Model
{
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Таблиця comments має commentable_id + commentable_type. Тож Comment може належати і Post, і Video без окремих таблиць. Бувають також many-to-many поліморфні зв'язки (morphToMany), напр. теги.

Докладніше в документації: Поліморфні зв’язки

У belongsToMany проміжна (pivot) таблиця зберігає зв'язки. Додаткові стовпці на ній оголошують через withPivot():

public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class)
        ->withPivot('assigned_at', 'is_primary')
        ->withTimestamps();
}

Доступ і керування:

$user->roles->first()->pivot->assigned_at;  // читання pivot-даних

$user->roles()->attach($roleId, ['is_primary' => true]); // додати
$user->roles()->detach($roleId); // прибрати
$user->roles()->sync([1, 2, 3]);// привести до набору
$user->roles()->updateExistingPivot($roleId, [...]); // оновити pivot

Для окремої моделі pivot використовують ->using(RoleUser::class).

Докладніше в документації: Many To Many зв’язки

Зовнішній ключ завжди лежить у таблиці тієї моделі, що належить іншій. Звідси й вибір методу.

// posts.user_id посилається на users.id
class User extends Model
{
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);   // ключ у чужій таблиці
    }
}

class Post extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class); // ключ у своїй таблиці
    }
}

Просте правило: у якій моделі колонка *_id, та модель і пише belongsTo. Інша сторона - hasOne чи hasMany.

Імена за замовчуванням:

  • hasMany шукає в posts колонку user_id - ім'я моделі-власника в snake_case плюс _id;
  • belongsTo бере ім'я методу плюс _id: метод author() шукатиме author_id, і тоді ключ доведеться передати явно, якщо колонка називається інакше.
public function author(): BelongsTo
{
    return $this->belongsTo(User::class, 'user_id');
}

Часта помилка новачків - написати hasOne там, де потрібен belongsTo, бо «в пості є один автор». Запит тоді шукатиме users.post_id, якого не існує.

Докладніше в документації: Зворотний бік один до багатьох

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.