Junior: питання на співбесіді з теми «Хелпери й рядки»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Laravel має два способи працювати з рядками, і обидва побудовані на тому самому наборі методів.
1. Статичний клас Str - кожен метод приймає рядок і повертає новий рядок:
use Illuminate\Support\Str;
Str::limit('Довгий текст статті', 10); // 'Довгий тек...'
Str::headline('laravel_framework'); // 'Laravel Framework'
Str::mask('olena@example.com', '*', 3); // 'ole**************'
Str::contains($title, ['Laravel', 'PHP']);
Str::uuid();
2. Fluent-рядки - Str::of() чи коротший хелпер str() повертають об'єкт Stringable, і методи можна складати в ланцюжок:
$slug = str(' Нова версія Laravel ')
->squish() // прибрати зайві пробіли
->limit(50, '')
->slug('-', 'uk');
Str::of($path)->afterLast('/')->beforeLast('.');
Між Str::of('...') і str('...') різниці немає: це той самий Stringable. А виклик str() без аргументів повертає об'єкт, що перенаправляє виклики до статичних методів Str: str()->uuid().
Коли що обирати:
| Ситуація | Що зручніше |
|---|---|
| одна операція | Str::limit($text, 100) |
| кілька перетворень поспіль | str($text)->squish()->lower()->... |
| умовні кроки | str($x)->when($cond, fn ($s) => $s->append('...')) |
Stringable - не рядок. Він приводиться до рядка в echo, Blade і конкатенації, але === з рядком дасть false, а функція з типом string у строгому режимі отримає об'єкт. Тому в кінці ланцюжка часто викликають ->toString() чи ->value():
$name = str($input)->trim()->title()->toString();
Кирилиця: методи Str враховують багатобайтові символи - str('Привіт')->length() повертає 6, а не 12, як strlen(). Але Str::slug() і Str::ascii() транслітерують за правилами мови, яку треба вказати явно, якщо результат має бути українською транслітерацією.
Перед тим як писати власну функцію для рядків варто переглянути список методів Str: там уже є between, excerpt, initials, wordCount, plural, squish, isUuid, isJson і десятки інших.
Клас Illuminate\Support\Number - обгортка над розширенням intl для форматування чисел з урахуванням локалі. Для більшості методів розширення intl обов'язкове.
use Illuminate\Support\Number;
Number::format(1234567.891, precision: 2, locale: 'uk'); // '1 234 567,89'
Number::currency(1500, in: 'UAH', locale: 'uk'); // '1 500,00 ₴'
Number::percentage(12.345, precision: 1); // '12.3%'
Number::fileSize(1536000, precision: 1); // '1.5 MB'
Number::abbreviate(1500000, precision: 1); // '1.5M'
Number::forHumans(1500000); // '2 million'
Number::spell(42, locale: 'uk'); // 'сорок два'
Number::ordinal(3); // '3rd'
Number::clamp($value, min: 1, max: 100);
Що варто знати:
precisionза замовчуванням 0 уfileSize,abbreviateіforHumans:Number::forHumans(1500000)округлює до'2 million'. Для дробової частини вказуйтеprecision;fileSizeрахує в степенях 1024, а не 1000;forHumansіabbreviateне локалізовані: вони завжди пишуть англійською (million,M);format,currency,percentage,spellприймаютьlocale, а в українській локалі роздільник тисяч - нерозривний пробіл, десятковий - кома.
Локаль для всього застосунку чи фрагмента:
// у сервіс-провайдері
Number::useLocale('uk');
Number::useCurrency('UAH');
// тимчасово для одного блоку
Number::withLocale('de', fn () => Number::format(1234.5)); // '1.234,5'
Зворотна операція - розібрати введене користувачем число з урахуванням локалі:
Number::parseFloat('1 234,5', locale: 'uk');
Гроші: Number::currency() - лише для відображення. Рахувати гроші через float не можна: зберігайте суму в копійках цілим числом (або в decimal у базі) і форматуйте тільки при виводі:
Number::currency($order->total_cents / 100, in: 'UAH');
У Blade це зручно загорнути в accessor моделі чи Blade-компонент, щоб формат валюти й точність були однаковими на всьому сайті.
Вкладені масиви (конфігурація, 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.