Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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() - і браузер завантажує прямо зі сховища.

Докладніше в документації: Відповіді: завантаження файлів