Laravel: колекції й хелпери
20 питань · ~20 хв · Версія v3.0
Увійдіть, щоб продовжити
Методи колекцій і повідомлення вищого порядку, ліниві колекції, макроси, хелпери масивів, рядків і чисел, retry і rescue, Pipeline - питання всіх рівнів, від junior до lead.
- За спробу
- 20
- У пулі
- 44
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
16 питаньCollection - обгортка над масивом із плавним, ланцюжковим API. Eloquent-запити повертають саме колекції.
$names = collect($users)
->filter(fn ($u) => $u->active)
->sortBy('name')
->map(fn ($u) => $u->name)
->values();
Десятки методів: map, filter, reduce, groupBy, pluck, each, sum. Код читається зрозуміліше за вкладені цикли й array_*-функції. Для дуже великих наборів є LazyCollection (на генераторах).
LazyCollection використовує PHP-генератори, щоб тримати в пам'яті лише один елемент за раз - критично для величезних наборів.
LazyCollection::make(function () {
$handle = fopen('huge.csv', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
})->filter(...)->take(100)->each(...);
З Eloquent:
User::cursor()->each(function ($user) { /* по одному рядку */ });
На відміну від звичайної Collection, методи не матеріалізують увесь набір - обчислення «ліниві» й виконуються лише при ітерації.
Колекції зручні настільки, що ними легко почати робити роботу бази - і заплатити памʼяттю та часом.
Типова помилка:
$active = Post::all()->where('is_published', true); // вся таблиця в памʼять
$count = Post::all()->count(); // теж уся таблиця
$latest = Post::all()->sortByDesc('created_at')->take(5); // і сортування в PHP
Кожен рядок витягує всі записи, щоб потім відкинути більшість.
Те саме в базі:
$active = Post::where('is_published', true)->get();
$count = Post::count();
$latest = Post::latest()->limit(5)->get();
Різниця не косметична: база фільтрує за індексом і повертає стільки, скільки потрібно.
Як розрізняти. Методи Builder виконуються в SQL, методи Collection - у PHP. Межа проходить там, де стоїть get(), all() чи first(): усе після них - уже PHP.
Плутанину створює однакова назва: where() є і в білдера, і в колекції. Post::where(...) фільтрує в базі, Post::all()->where(...) - у памʼяті.
Коли колекція доречна:
- Дані вже отримані, і потрібне ще одне групування чи перетворення - другий запит дорожчий.
- Логіка не виражається в SQL: складна умова на PHP, робота з обʼєктами-значеннями.
- Набір свідомо малий - десятки рядків, де різниці немає.
Коли ні: фільтрація, підрахунок, сортування й обмеження на таблиці, розмір якої росте. Це робота бази.
Проміжний варіант - lazy(): обхід пачками з синтаксисом колекції, коли обробити треба всі рядки, але тримати їх у памʼяті не можна.
Вкладені масиви (конфігурація, JSON зі стороннього API, payload вебхука) зручно читати крапковою нотацією замість ланцюжка isset:
use Illuminate\Support\Arr;
$data = [
'user' => ['profile' => ['name' => 'Olena']],
'orders' => [['total' => 10], ['total' => 20]],
];
Arr::get($data, 'user.profile.name'); // 'Olena'
Arr::get($data, 'user.email', 'n/a'); // 'n/a' - значення за замовчуванням
Arr::has($data, 'user.profile.name'); // true
data_get - те саме, але для масивів і об'єктів разом і з підтримкою *:
data_get($data, 'orders.*.total'); // [10, 20]
data_get($request->json(), 'payment.status');
data_get($model, 'author.profile.city'); // працює й з Eloquent-моделями
Запис:
Arr::set($config, 'mail.from.address', 'hello@example.com'); // за посиланням
data_set($data, 'orders.*.paid', false); // усім елементам
data_fill($data, 'meta.source', 'api'); // лише якщо ключа ще немає
Arr::forget($data, 'user.password');
Інші корисні методи:
| Метод | Що робить |
|---|---|
Arr::only($a, ['name', 'email']) |
лише вказані ключі |
Arr::except($a, ['password']) |
усі, крім вказаних |
Arr::wrap($value) |
гарантує масив: null → [], 'a' → ['a'] |
Arr::dot($a) / Arr::undot($a) |
вкладений масив ↔ плаский з ключами a.b.c |
Arr::pluck($a, 'user.name') |
значення поля з кожного елемента |
Arr::first($a, fn ($v) => ...) |
перший елемент за умовою |
Arr::string($a, 'key'), Arr::integer(...) |
значення з перевіркою типу, інакше виняток |
Пастка крапкової нотації: якщо в ключі масиву справді є крапка ('version.major' => 1), Arr::get($a, 'version.major') спершу перевіряє точний ключ і знайде його, але data_get і запис через Arr::set трактуватимуть крапку як вкладеність. Для таких даних краще звичайний доступ $a['version.major'].
Arr::wrap особливо корисний для параметрів, що приймають «одне значення або масив»: foreach (Arr::wrap($recipients) as $email) - без перевірок типу.
Для колекцій є ті самі можливості: collect($data)->get(...), ->pluck('user.name'), ->dot(). Якщо дані обробляються ланцюжком перетворень - колекція зручніша, для одного звернення - Arr чи data_get.
Усі три повертають нову колекцію (чи значення) і не змінюють вихідну.
$prices = collect([100, 250, 40]);
$withVat = $prices->map(fn (int $p) => $p * 1.2); // [120, 300, 48]
$expensive = $prices->filter(fn (int $p) => $p > 50); // [0 => 100, 1 => 250]
$total = $prices->reduce(fn (int $sum, int $p) => $sum + $p, 0); // 390
$prices->all(); // [100, 250, 40] - без змін
map()- перетворює кожен елемент, кількість та сама;filter()- залишає елементи, що пройшли умову (без замикання - прибирає «порожні»);reduce()- зводить колекцію до одного значення будь-якого типу.
Пастка filter(): ключі зберігаються - вийде [0 => 100, 1 => 250], а після видалення середнього елемента було б [0 => ..., 2 => ...]. У JSON такий масив стане об'єктом. Перед відповіддю викликають values().
Мутуючі методи теж є, і їх варто знати, щоб не здивуватися: transform(), push(), put(), pop(), shift(), forget() змінюють саму колекцію.
Для простих сум є готові sum(), avg(), max() - reduce() потрібен для складнішого накопичення.
Прочитати - ще не значить знати
20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.