Junior: питання на співбесіді з теми «Відповіді й URL»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Контролер чи замикання маршруту може повернути майже будь-що - Laravel сам перетворить результат на об'єкт Response:
| Що повертаємо | Що отримує клієнт |
|---|---|
| рядок | text/html з цим рядком |
| масив | JSON |
| модель Eloquent чи колекція | JSON (з урахуванням $hidden, $appends) |
view('posts.show', [...]) |
HTML з шаблону |
response(...) / response()->json(...) |
повний контроль: статус, заголовки, cookie |
redirect(...) |
відповідь 302 з заголовком Location |
| API Resource | JSON у форматі ресурсу |
об'єкт з інтерфейсом Responsable |
те, що поверне його toResponse() |
Route::get('/ping', fn () => 'pong');
Route::get('/settings', fn () => ['theme' => 'dark']);
Route::get('/users/{user}', fn (User $user) => $user);
return response('Created', 201)
->header('X-Request-Id', $requestId)
->cookie('seen_intro', '1', minutes: 60 * 24 * 30);
return response()->json(['message' => 'Accepted'], 202);
return response()->noContent(); // 204
Чому краще явні відповіді для API:
- модель напряму віддає всі її атрибути, крім
$hidden. Додали нову колонку в таблицю - вона автоматично з'явилася в API. API Resource описує формат явно; - статус-код: масив чи модель завжди повертають 200, а створення ресурсу - це 201, видалення - 204;
- заголовки (кешування, розташування нового ресурсу) задаються лише через об'єкт відповіді.
Відповідь має бути повернута: її треба саме повернути з контролера. Виклик response(...) без return нічого не робить. Щоб перервати виконання з будь-якого місця, використовують виняток: abort(404), throw ValidationException::withMessages(...) чи власний виняток з методом render().
Middleware бачить уже готову відповідь: після $next($request) можна додати заголовки чи cookie до будь-якої відповіді, незалежно від того, що повернув контролер.
Основні способи:
return redirect('/dashboard'); // за шляхом
return redirect()->route('posts.show', $post); // за іменем маршруту (модель підставить ключ)
return to_route('posts.show', ['post' => $post]); // коротша форма
return redirect()->action([PostController::class, 'index']);
return back(); // на попередню сторінку
return redirect()->away('https://partner.example.com'); // на зовнішній домен без перевірки URL
Разом з повідомленням - флеш-дані сесії, доступні лише в наступному запиті:
return redirect()->route('posts.index')
->with('status', 'Публікацію збережено.');
@if (session('status'))
<div class="alert">{{ session('status') }}</div>
@endif
Повернути з введеними даними й помилками (якщо перевіряєте вручну, а не через валідацію):
return back()
->withInput()
->withErrors(['email' => 'Цю адресу вже зареєстровано.']);
У формі введене повертається через old('email'), помилки - через @error('email'). Валідація Laravel робить це сама: при помилці вона кидає виняток, що перетворюється саме на такий редирект.
Патерн Post/Redirect/Get: після успішної обробки форми (POST) - завжди редирект, а не відображення сторінки. Інакше оновлення сторінки в браузері повторно відправить форму: другий платіж, дубль коментаря.
Типові помилки:
redirect()безreturn- нічого не відбувається, виконання триває;back()без запасного варіанта: якщо користувач прийшов за прямим посиланням, «назад» може вести на зовнішній сайт чи нікуди.back(fallback: route('home'));- редирект за URL з параметра запиту (
redirect($request->input('next'))) - відкритий редирект, яким користуються фішери. Для повернення після входу -redirect()->intended(...), а сторонні адреси - лише зі списку дозволених; away()для внутрішніх адрес - він не перевіряє URL взагалі; для своїх сторінок -route().
Коди: redirect() за замовчуванням дає 302. Постійне перенесення сторінки (SEO) - 301: redirect('/new', 301) чи Route::permanentRedirect('/old', '/new') у маршрутах.
Три методи для трьох ситуацій:
1. download - завантажити файл, що вже є на диску:
return response()->download(storage_path('app/reports/2026-q3.pdf'));
return response()->download($path, 'Звіт за III квартал.pdf'); // інша назва для користувача
return response()->download($path)->deleteFileAfterSend(); // тимчасовий файл
Заголовок Content-Disposition: attachment - браузер пропонує зберегти файл.
2. file - показати файл у браузері (зображення, PDF):
return response()->file(storage_path('app/invoices/42.pdf'));
Content-Disposition: inline - PDF відкриється у вбудованому переглядачі.
3. streamDownload - файл, якого немає на диску, генерується на льоту:
return response()->streamDownload(function () {
$out = fopen('php://output', 'w');
fputcsv($out, ['id', 'email']);
User::query()->lazyById(1000)->each(function (User $user) use ($out) {
fputcsv($out, [$user->id, $user->email]);
});
fclose($out);
}, 'users.csv', ['Content-Type' => 'text/csv']);
Дані віддаються частинами - експорт мільйона рядків не займає мільйона рядків пам'яті й не створює тимчасових файлів.
З дисків Storage (зокрема S3):
return Storage::download('reports/2026-q3.pdf', 'report.pdf');
return Storage::disk('s3')->response('avatars/42.jpg'); // показати
Безпека - головне при віддачі файлів:
- шлях ніколи не будується з введення користувача напряму:
download(storage_path('app/' . $request->file))дозволить завантажити../../.env(обхід шляху). Файл шукають за ідентифікатором у базі, де записано шлях; - перевірка прав перед віддачею - політика чи
Gate, як для будь-якого ресурсу; - приватні файли не кладуть у
public/чи публічний диск - вони мають віддаватися лише через контролер з перевіркою; - назва файлу від користувача - через
download($path, $safeName), Laravel сам коректно закодує не-ASCII символи в заголовку.
Для великих файлів на S3 краще не проганяти їх через PHP взагалі: тимчасове підписане посилання Storage::temporaryUrl() - і браузер завантажує прямо зі сховища.