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

Статті за тегом «Eloquent»

Статті, новини, туторіали та переклади від учасників спільноти

No results.
Tips 04 серпня 2026

Колекції Eloquent - це більше, ніж масиви

Eloquent повертає Collections, а не масиви.

Помилка - одразу викликати toArray() і працювати як з масивом.

Переваги колекцій:

  • Ланцюжки операцій
  • Зручні методи (map, filter, groupBy, pluck...)
  • Більш виразний код

Порада: тримайте дані в Collection якомога довше.

$users = User::all(); // Collection

// ПОГАНО
foreach ($users->toArray() as $user) {
  echo $user['name'];
}

// ДОБРЕ
$users->each(function ($user) {
  echo $user->name;
});

// Фільтрація й трансформація
$activeAdmins = $users
  ->where('active', true)
  ->where('is_admin', true)
  ->pluck('email');

// Групування
$byRole = $users->groupBy('role');

// Пара "id => name"
$names = $users->pluck('name', 'id');

// Перевірка
$hasAdmin = $users->contains('is_admin', true);
$hasAdult = $users->contains(fn($user) => $user->age > 18);
Tips 03 серпня 2026

chunkById() - безпечна пакетна обробка

Обробляєте таблицю через chunk() і одночасно змінюєте колонку, за якою фільтруєте? Частина записів буде пропущена.

chunk() гортає сторінки через OFFSET. Якщо запис випадає з вибірки під час обробки, решта зсувається - і цілий пакет ви не побачите.

Як працює chunkById():

Замість OFFSET використовує where('id', '>', $lastId). Зсув вибірки більше не ламає ітерацію.

Коли обов'язково chunkById():

  • Оновлюєте поле, за яким фільтруєте
  • Видаляєте записи під час обходу
  • Будь-яка міграція даних із записом у ту саму таблицю

Порада: сортування задає сам chunkById() - не додавайте власний orderBy(), він його перезапише.

// НЕБЕЗПЕЧНО: пропустить половину записів
User::where('notified', false)
  ->chunk(100, function ($users) {
    foreach ($users as $user) {
      $user->update(['notified' => true]);
      // Вибірка "з'їдається", OFFSET перестрибує записи
    }
  });

// БЕЗПЕЧНО: курсор по первинному ключу
User::where('notified', false)
  ->chunkById(100, function ($users) {
    foreach ($users as $user) {
      $user->update(['notified' => true]);
    }
  });
// Видалення під час обходу - тільки chunkById()
Log::where('created_at', '<', now()->subYear())
  ->chunkById(500, function ($logs) {
    $logs->each->delete();
  });

// Власна колонка-курсор (наприклад, UUID-таблиця)
Order::where('status', 'pending')
  ->chunkById(200, function ($orders) {
    $orders->each->process();
  }, column: 'uuid');

// Читання без змін - звичайний chunk() достатній
Product::chunk(1000, function ($products) {
  foreach ($products as $product) {
    $this->exportToCsv($product);
  }
});

// each() - те саме, але по одному запису
User::where('active', true)->eachById(function ($user) {
  $user->recalculateStats();
}, 200);
Tips 31 липня 2026

firstOrFail() - чиста обробка 404

Перевіряєте на існування моделі перед використанням? Багато шаблонного коду.

firstOrFail() кидає виняток, який Laravel перетворює на 404.

Переваги:

  • Менше boilerplate
  • Автоматична обробка помилок
  • REST-friendly

Схожі методи: findOrFail($id), sole().

// ПОГАНО
public function show($id)
{
  $post = Post::find($id);

  if (!$post) {
    abort(404);
  }

  return view('posts.show', compact('post'));
}

// ДОБРЕ
public function show($id)
{
  $post = Post::findOrFail($id);

  return view('posts.show', compact('post'));
}

// За slug
public function showBySlug($slug)
{
  $post = Post::where('slug', $slug)->firstOrFail();

  return view('posts.show', compact('post'));
}
Tips 30 липня 2026

Глобальні скоупи - умови за замовчуванням

Одна й та сама where-умова в кожному запиті? Це не DRY.

Global Scopes додають умову автоматично.

Типові кейси:

  • Multi-tenancy
  • Soft deletes (вже реалізовано через глобальний скоуп)
  • published = true
  • Архівні записи

Можна прибрати через withoutGlobalScope().

// Окремий клас скоупу
class PublishedScope implements Scope
{
  public function apply(Builder $builder, Model $model)
  {
    $builder->where('published', true);
  }
}

// Підключення до моделі
class Post extends Model
{
  protected static function booted()
  {
    static::addGlobalScope(new PublishedScope);
  }
}

// Всі запити - тепер тільки published
$posts = Post::all();
$post = Post::find(1);
// Зняття скоупу для окремого запиту
$all = Post::withoutGlobalScope(
  PublishedScope::class
)->get();

// Multi-tenancy через глобальний скоуп
class TenantScope implements Scope
{
  public function apply(Builder $builder, Model $model)
  {
    if (auth()->check()) {
      $builder->where(
        'tenant_id',
        auth()->user()->tenant_id
      );
    }
  }
}

class Document extends Model
{
  protected static function booted()
  {
    static::addGlobalScope(new TenantScope);
  }
}
Tips 29 липня 2026

Subquery Selects - уникаємо N+1 без eager loading

Потрібен останній пост користувача? Eager loading витягне ВСІ пости.

Subquery-select дозволяє додати обчислювану колонку без завантаження зв'язку.

Коли використовувати:

  • Останній/перший пов'язаний запис
  • count/sum/max/min по зв'язку
  • Коли не потрібні повні дані зв'язку

Продуктивність:

Один запит. Ніякого N+1. Немає зайвих даних.

// ПОГАНО: вантажимо всі пости, щоб узяти останній
$users = User::with('posts')->get();
foreach ($users as $user) {
  echo $user->posts
    ->sortByDesc('created_at')->first()->title;
}

// ДОБРЕ: subquery - тільки заголовок останнього поста
$users = User::select('users.*')
  ->addSelect(['last_post_title' => Post::select('title')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->limit(1)
  ])->get();

echo $users[0]->last_post_title;

// Сортування по subquery - без завантаження постів
$users = User::orderByDesc(
  Post::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->limit(1)
)->get();
Tips 28 липня 2026

whereHas() проти has() - фільтр за зв'язками

Треба знайти користувачів, у яких є пости? Або тільки з опублікованими постами?

has() перевіряє факт наявності зв'язку. whereHas() дозволяє додати умови до зв'язку.

Різниця:

  • has('posts') - користувачі з будь-якими постами
  • whereHas('posts', fn($q) => ...) - користувачі з конкретними постами

Порада по продуктивності:

Використовуйте has(), коли немає додаткових умов - він простіший.

// Користувачі з будь-якими постами
$users = User::has('posts')->get();

// Користувачі з мінімум 5 постами
$users = User::has('posts', '>=', 5)->get();

// Користувачі тільки з опублікованими постами
$users = User::whereHas('posts', function ($query) {
  $query->where('published', true);
})->get();

// Більш складні умови
$users = User::whereHas('posts', function ($query) {
  $query->where('published', true)
    ->where('views', '>', 1000);
})->get();

// Користувачі без постів
$users = User::doesntHave('posts')->get();

// Комбінування кількох зв'язків
$users = User::has('posts')
  ->whereHas('comments', fn($q) => $q->where('spam', false))
  ->get();
Tips 27 липня 2026

touch() - автооновлення timestamps у батьків

Додали коментар до поста, а updated_at у поста не змінився. Кеш не оновився.

touch() дозволяє автоматично оновлювати час оновлення батьківської моделі.

Як працює:

У дочірній моделі вказуємо $touches. Коли вона зберігається - оновлюється updated_at у батьків.

Де корисно:

  • Інвалідація кешу за timestamp
  • "Остання активність"
  • Відстеження змін контенту

Працює з будь-якими зв'язками.

class Comment extends Model
{
  // Авто-touch для поста
  protected $touches = ['post'];

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

// Тепер при створенні/оновленні коментаря
$comment = new Comment(['body' => 'Great post!']);
$comment->post()->associate($post);
$comment->save();
// updated_at у Post буде оновлено

// Ручне оновлення timestamps
$post->touch();

// Touch кількох зв'язків
class Comment extends Model
{
  protected $touches = ['post', 'author'];
}
Tips 24 липня 2026

whereBelongsTo() - чисті запити по зв'язках

Фільтруєте за зв'язком через where('user_id', $user->id)? Можна краще.

whereBelongsTo() сам підставляє зовнішній ключ.

Переваги:

  • Не треба пам'ятати назву foreign key
  • Працює з кастомним ключем
  • Краще читається
  • Безпечніше при рефакторингу

Бонус: працює з масивом моделей.

Порада: дуже зручно в policy-методах, де фільтруєте по auth()->user().

// ПОГАНО: ручний foreign key
$posts = Post::where('user_id', $user->id)->get();
$comments = Comment::where('post_id', $post->id)->get();

// ДОБРЕ: Laravel сам визначає ключ
$posts = Post::whereBelongsTo($user)->get();
$comments = Comment::whereBelongsTo($post)->get();

// Кілька зв'язків
$comments = Comment::whereBelongsTo($user)
  ->whereBelongsTo($post)
  ->get();

// Кастомна назва зв'язку
$posts = Post::whereBelongsTo($user, 'author')->get();

// У policy - максимально чисто
public function update(User $user, Post $post)
{
  return Post::whereBelongsTo($user)->exists();
}
Tips 23 липня 2026

Події моделей - хуки в життєвий цикл Eloquent

Треба надіслати email після реєстрації? Або залогувати видалення поста?

Model Events дозволяють підключатися до create/update/delete.

Доступні події:

  • creating / created
  • updating / updated
  • deleting / deleted
  • saving / saved

Два підходи:

  • Окремі слухачі (listeners)
  • booted() у моделі (швидкий варіант)

Порада: використовуйте creating для генерації UUID/slug до збереження.

class User extends Model
{
  protected static function booted()
  {
    // Перед створенням
    static::creating(function ($user) {
      $user->uuid = Str::uuid();
    });

    // Після створення
    static::created(function ($user) {
      $user->sendWelcomeEmail();
    });

    // Перед видаленням - каскад
    static::deleting(function ($user) {
      $user->posts()->delete();
    });

    // Повернення false скасовує операцію
    static::creating(function ($user) {
      if ($user->isBanned()) {
        return false;
      }
    });
  }
}
// Оновлення пов'язаних моделей
class Order extends Model
{
  protected static function booted()
  {
    static::created(function ($order) {
      $order->user->increment('total_orders');
    });

    static::deleted(function ($order) {
      $order->user->decrement('total_orders');
    });

    static::updating(function ($order) {
      if ($order->isDirty('status')) {
        ActivityLog::create([
          'order_id' => $order->id,
          'old_status' => $order->getOriginal('status'),
          'new_status' => $order->status,
        ]);
      }
    });
  }
}

Перевір себе

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

Матеріали за темою «Eloquent»: усе, що ми писали про це українською. Нижче - суміжні теми, якщо шукаєте ширше.

Зараз опубліковано 54 матеріали від 3 авторів. Останній матеріал - 28 жовтня 2026.