Блог
Статті, новини, туторіали та переклади від учасників спільноти
sole() - суворе отримання одного результату
Запит має повернути рівно один запис. Повертає нуль або декілька? Це баг.
first() мовчки повертає null. sole() кидає виняток якщо не рівно один.
Коли використовувати:
- Отримання за унікальним ідентифікатором
- Коли декілька результатів вказують на пошкодження даних
- Критична бізнес-логіка
Винятки що кидаються:
RecordsNotFoundException- нуль результатівMultipleRecordsFoundException- більше одного
Порада: Використовувати у фінансових операціях де очікується рівно один запис.
// ПОГАНО: Мовчки повертає null або перший з багатьох
$user = User::where('email', $email)->first();
// Що якщо є дублікати? Мовчазний баг.
// ДОБРЕ: Гарантує рівно один результат
try {
$user = User::where('email', $email)->sole();
// Гарантовано єдиний користувач що підходить
} catch (RecordsNotFoundException $e) {
// Користувач не знайдений
} catch (MultipleRecordsFoundException $e) {
// Пошкодження даних - існують дублікати
}
// В обробці рахунків
$invoice = Invoice::where('invoice_number', $number)->sole();
// Дублікати були б серйозним багом
// firstOr vs soleOr
$user = User::where('email', $email)->soleOr(function () {
throw new UserNotFoundException();
});
// Зі зв'язками
$user = User::with('profile')->where('id', $id)->sole();
cursor() - потокове передавання великих наборів даних
При експорті 100,000 користувачів. get() завантажує все в пам'ять. Сервер падає.
cursor() передає результати один за одним. Константне використання пам'яті.
Як це працює:
Використовує MySQL cursor. Отримує один запис за раз. Пам'ять залишається низькою.
Коли використовувати:
- Експорт великих наборів даних
- Обробка величезних таблиць
- Середовища з обмеженою пам'яттю
Компроміс:
Тримає з'єднання з базою даних відкритим довше. Не для веб-запитів, ідеально для команд.
Порада: Використовувати в консольних командах та jobs, уникай в HTTP контролерах.
// ПОГАНО: Завантажує 100k записів в пам'ять
$users = User::all(); // Може впасти
foreach ($users as $user) {
$this->export($user);
}
// ДОБРЕ: Потокове передавання один за одним
foreach (User::cursor() as $user) {
$this->export($user); // Константна пам'ять
}
// В консольній команді
public function handle()
{
$this->output->progressStart(User::count());
foreach (User::cursor() as $user) {
$this->processUser($user);
$this->output->progressAdvance();
}
$this->output->progressFinish();
}
// З умовами
foreach (User::where('active', true)->cursor() as $user) {
// Обробляти тільки активних користувачів
}
exists() vs count() - розумна перевірка
Перевірка чи користувач має пости? Використання $user->posts->count() > 0 завантажує всі пости.
exists() зупиняється на першому збігу. Набагато швидше.
Різниця:
count()підраховує всі записиexists()зупиняється коли знаходить перший
Вплив на продуктивність:
З 10,000 постів - count() завантажує всі, exists() знаходить один і зупиняється.
Порада: Використання exists() для булевих перевірок, count() тільки коли потрібне фактичне число.
// ПОГАНО: Завантажує всі пости, рахує їх
if ($user->posts->count() > 0) {
echo "Користувач має пости";
}
// ДОБРЕ: Зупиняється на першому пості
if ($user->posts()->exists()) {
echo "Користувач має пости";
}
// Перевірка чи зв'язок порожній
if ($user->posts()->doesntExist()) {
echo "Пости не знайдено";
}
// Рівень запиту
if (Post::where('published', true)->exists()) {
// Принаймні один опублікований пост існує
}
// Коли справді потрібна кількість
$postCount = $user->posts()->count();
echo "Користувач має {$postCount} постів";
// Перевірка порожнього на завантаженому зв'язку
if ($user->posts->isEmpty()) {
// Вже завантажено, без додаткових запитів
}
whereIn() з великими масивами - пастка пам'яті
Передавання 10,000 ID в whereIn()? Рядок запиту стає величезним.
MySQL має ліміти розміру запиту. PHP використовує тонни пам'яті.
Кращий підхід:
Використовувати підзапит або тимчасову таблицю для великих наборів даних.
Продуктивність:
whereIn()з 100 елементами - нормальноwhereIn()з 10,000 елементами - проблема- Підзапит - константна пам'ять
Порада: Якщо масив має більше 1000 елементів, розглянь використання підзапиту або chunking.
// ПОГАНО: Великий масив в whereIn
$userIds = range(1, 10000); // 10k ID
$users = User::whereIn('id', $userIds)->get();
// Величезний запит, високе використання пам'яті
// ДОБРЕ: Використовувати підзапит
$activeUserIds = DB::table('user_activity')
->where('last_login', '>', now()->subDays(30))
->select('user_id');
$users = User::whereIn('id', $activeUserIds)->get();
// Альтернатива: Join
$users = User::join('user_activity', 'users.id', '=',
'user_activity.user_id')
->where('user_activity.last_login', '>', now()->subDays(30))
->select('users.*')
->get();
// Розділяй великі операції
$userIds = range(1, 10000);
collect($userIds)->chunk(500)->each(function ($chunk) {
User::whereIn('id', $chunk)->update(['notified' => true]);
});
limit() - не завантажувати непотрібне
При завантаженні 50,000 записів для показу 10 на екрані.
Додаток зависає. Серверу бракує пам'яті. Користувач закриває браузер через 30 секунд.
Проблема:
Без limit(), Eloquent завантажує все в пам'ять. Пагінація допомагає, але все одно потрібен limit() в багатьох випадках.
Коли використовувати:
- Попередні перегляди та зразки
- Останні N елементів
- Топ виконавці
- Будь-який сценарій "показати перші декілька"
Порада: Поєднуй з orderBy() для консистентних результатів. Без сортування limit() повертає випадкову підмножину.
// ПОГАНО: Завантажує все
$products = Product::where('active', true)->get();
// Повертає 50,000 продуктів в пам'яті
// ДОБРЕ: Тільки те що потрібно
$featured = Product::where('featured', true)
->orderBy('created_at', 'desc')
->limit(5)
->get();
// Останні 10 замовлень
$recent = Order::latest()->limit(10)->get();
// Топ 3 користувачі за балами
$leaders = User::orderBy('points', 'desc')->limit(3)->get();
// Попередній перегляд дашборду
$stats = [
'recent_orders' => Order::latest()->limit(5)->get(),
'top_products' => Product::orderBy('sales', 'desc')
->limit(10)->get(),
'new_users' => User::latest()->limit(20)->get(),
];
// Без orderBy - непередбачувані результати
$random = Post::limit(5)->get(); // ПОГАНО
$latest = Post::latest()->limit(5)->get(); // ДОБРЕ
select() - припинити завантаження зайвих колонок
Завантаження повного рядка користувача з 20 колонками. Потрібні тільки ім'я та email.
База даних надсилає всі дані. Пам'ять витрачається. Запит повільніший.
Рішення:
Використовувати select() щоб вказати точні колонки. Швидші запити, менше пам'яті.
Важливо:
Завжди включай первинний ключ (id) щоб зв'язки працювали.
Просунуте:
Використовувати addSelect() щоб додати колонки до існуючого select.
Порада: Особливо важливо для таблиць з TEXT/BLOB колонками або таблиць з 20+ колонками.
// ПОГАНО: Завантажує всі колонки
$users = User::all();
foreach ($users as $user) {
echo $user->name; // Інші 18 колонок витрачені даремно
}
// ДОБРЕ: Тільки те що потрібно
$users = User::select('id', 'name', 'email')->get();
// Додавати колонки динамічно
$query = User::select('id', 'name');
if ($needEmail) {
$query->addSelect('email');
}
// Зі зв'язками - потрібні зовнішні ключі
$posts = Post::select('id', 'title', 'user_id')
->with('author:id,name')
->get();
// Агрегація без завантаження моделей
$totalViews = Post::sum('views');
$avgRating = Post::avg('rating');
$maxPrice = Product::max('price');
Laravel 13: AI SDK, JSON:API Resources і простіше оновлення
Вийшов Laravel 13 - велике оновлення без звичного болю при переході.
Route Caching - миттєвий приріст швидкості
Файл маршрутів завантажується при кожному запиті. З 500+ маршрутами це багато.
Кешування маршрутів компілює маршрути в один закешований файл. В 10 разів швидше.
Як використовувати:
Запустити php artisan route:cache після деплою. Маршрути завантажуються з кешу, а не з файлів.
Важливі обмеження:
Closure маршрути НЕ працюють з кешуванням. Виникнуть помилки. Всі маршрути повинні використовувати синтаксис controller@method.
Найкраща практика:
- Ніколи не використовувати closures в маршрутах - завжди контролери
- Кешувати маршрути тільки в продакшені
- Очищати кеш коли додаються нові маршрути
- Додати до скрипту деплою
Порада: Поєднувати з config:cache та view:cache для максимальної продуктивності.
// ПОГАНО: Не працює з route:cache
Route::get('/users', function () {
return User::all();
});
// ДОБРЕ: Працює з кешуванням
Route::get('/users', [UserController::class, 'index']);
// Команди деплою
php artisan config:cache // Кешувати конфіг
php artisan route:cache // Кешувати маршрути
php artisan view:cache // Кешувати view
// Розробка - очистити всі кеші
php artisan optimize:clear
// Перевірити що маршрути закешовані
php artisan route:list
// Має завантажуватись миттєво якщо закешовано
// В composer.json - авто-кеш після деплою
"scripts": {
"post-autoload-dump": [
"@php artisan route:cache"
]
}
Статті, новини та посібники про Laravel і PHP українською: релізи фреймворку, патерни, інструменти та досвід команд. Нижче - розбивка за категоріями й темами, якщо хочете звузити пошук.
Зараз опубліковано 301 матеріал від 3 авторів. Останній матеріал - 23 жовтня 2026.
- Категорії
- Новини 168 Tips & Tricks 71 Посібники 37 Нотатки 20 Переклади 5
- Теми
- Laravel 189 PHP 119 релізи 54 Eloquent 53 Laravel 13 41 Бекенд 41 Патерни проєктування 34 безпека 29 Продуктивність 29 пакети 24