Senior: питання на співбесіді з теми «Хелпери й рядки»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
2 питання
Багато класів Laravel використовують трейт Macroable: Str, Stringable, Arr, Collection, Request, Builder, Http, Number, Uri та інші. Макрос додає метод «ззовні» без успадкування.
// AppServiceProvider::boot()
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
Str::macro('phone', function (string $value): string {
return preg_replace('/\D+/', '', $value);
});
Stringable::macro('phone', function (): Stringable {
return new Stringable(Str::phone($this->value));
});
Str::phone('+38 (067) 123-45-67'); // '380671234567'
str('+38 (067) 123-45-67')->phone(); // Stringable
Важливі деталі:
StrіStringable- різні класи: макрос наStrне з'являється у fluent-ланцюжку. Для обох стилів потрібні два макроси;$thisу замиканні прив'язується до екземпляра (дляStringable- до об'єкта з властивістюvalue), а в статичному виклику - ні;mixinреєструє кілька макросів з класу, кожен публічний чи захищений метод якого повертає замикання:
Str::mixin(new StrMixin);
Ризики:
- Конфлікт з майбутніми методами фреймворку. Макроси викликаються через
__call/__callStatic, тобто лише якщо справжнього методу немає. Якщо в наступній версії Laravel з'явитьсяStr::phone()з іншою поведінкою, ваш макрос мовчки перестане викликатися. Захист - префікси (Str::appPhone) і тести на поведінку макросів; - Невидимість для інструментів: IDE й PHPStan не бачать макросів без додаткових анотацій чи ide-helper; автодоповнення зникає, а аналізатор лається на «невідомий метод»;
- Глобальний стан: макрос зареєстрований для всього процесу. У пакеті він може зіткнутися з макросом іншого пакета чи застосунку з тим самим ім'ям - перемагає той, хто зареєструвався останнім;
- Прихована логіка: бізнес-правила в макросах (
Str::orderNumber()) важче знайти й тестувати, ніж у звичайному класі.
Коли макрос доречний: невелика загальна утиліта, що природно продовжує API класу (форматування телефону, нормалізація пробілів) і використовується в багатьох місцях.
Коли краще звичайний клас чи value object: доменна логіка, щось із залежностями, чи метод, потрібний в одному модулі. PhoneNumber::fromString($raw)->normalized() явніший, тестується окремо й підтримується IDE без підказок.
Склеювання URL рядками - джерело тонких помилок: забутий ? чи &, подвійне кодування, втрачені параметри, // у шляху, неекранований пробіл.
// крихко
$url = $base . '/search?q=' . $query . '&page=' . $page;
Illuminate\Support\Uri (на основі бібліотеки League URI) дає незмінний (immutable) об'єкт для розбору й зміни адрес:
use Illuminate\Support\Uri;
$uri = Uri::of('https://example.com/search?q=php')
->withQuery(['page' => 2]) // злиття з наявними параметрами
->withFragment('results');
(string) $uri; // 'https://example.com/search?q=php&page=2#results'
$uri->host(); // 'example.com'
$uri->path(); // 'search'
$uri->query()->get('q'); // 'php'
$uri->pathSegments(); // колекція сегментів
Змінення параметрів запиту:
| Метод | Що робить |
|---|---|
withQuery([...]) |
додає й перезаписує параметри (merge: false - замінює всі) |
withQueryIfMissing([...]) |
лише відсутні - зручно для значень за замовчуванням |
withoutQuery(['utm_source']) |
прибрати параметри |
replaceQuery([...]) |
замінити весь рядок запиту |
pushOntoQuery('tags', 'php') |
додати значення до масиву tags[] |
Кожен with... повертає новий об'єкт - вихідний не змінюється, тож базову адресу можна безпечно перевикористовувати.
Інтеграція з маршрутизацією:
Uri::route('posts.show', ['post' => $post]);
Uri::signedRoute('unsubscribe', ['user' => $user]);
Uri::temporarySignedRoute('download', now()->addHour(), ['file' => $file]);
return Uri::to('/dashboard')->withQuery(['tab' => 'billing'])->redirect();
Поточний запит: $request->uri() повертає той самий об'єкт, тож «поточна сторінка з іншим фільтром» будується без ручного розбору $_GET:
$request->uri()->withQuery(['sort' => 'price'])->withoutQuery(['page']);
Де це особливо важливо:
- очищення URL від трекінгових параметрів перед збереженням чи порівнянням;
- побудова посилань на сторонні API з набором параметрів;
- перевірка редиректів:
Uri::of($next)->host()замість регулярних виразів - щоб не відкрити відкритий редирект на чужий домен.
Обмеження: Uri не валідує, що адреса безпечна чи досяжна. Для SSRF-захисту потрібна окрема перевірка схеми, хоста й IP-адреси, до якої він резолвиться.