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

Питання на співбесіді: Маршрутизація

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

9 питань

Маршрутизація визначає, як застосунок відповідає на запити: вона прив'язує URL та HTTP-метод до конкретної дії - замикання або методу контролера.

Маршрути оголошуються у файлах директорії routes:

  • web.php - веб-інтерфейс із сесіями, cookie та CSRF-захистом.
  • api.php - stateless API (без сесій, з префіксом /api).
  • console.php - команди та розклад.

Laravel підтримує методи для кожного HTTP-дієслова - Route::get, post, put, patch, delete, а також Route::match([...]) і Route::any:

Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');

// Групи: спільні middleware, префікс, простір імен
Route::middleware('auth')->prefix('admin')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

Ключові можливості:

  • {post} - параметр маршруту; його можна одразу резолвити в модель (Route Model Binding).
  • ->name('posts.show') - іменований маршрут для генерації URL через route('posts.show', $post).
  • Route::resource(...) - одразу 7 RESTful-маршрутів для CRUD.

Переглянути всі зареєстровані маршрути: php artisan route:list.

Докладніше в документації: Маршрутизація

Route Model Binding автоматично резолвить параметр маршруту в екземпляр моделі - не треба вручну робити findOrFail().

Route::get('/posts/{post}', [PostController::class, 'show']);

// $post - вже знайдена модель
public function show(Post $post)
{
    return view('posts.show', compact('post'));
}

За замовчуванням пошук іде за id. Прив'язати за іншим стовпцем: {post:slug}. Якщо запис не знайдено - автоматично 404.

Докладніше в документації: Route Model Binding

Іменований маршрут має псевдонім, за яким генерують URL - замість хардкоду шляху.

Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
route('posts.show', $post); // у PHP
<a href="{{ route('posts.show', $post) }}">Деталі</a>

Навіщо: якщо URL зміниться (/posts → /articles), достатньо поправити маршрут - усі посилання оновляться автоматично. Також redirect()->route('posts.show', $post).

Докладніше в документації: Іменовані маршрути

Route::resource() одним рядком реєструє 7 маршрутів за RESTful-конвенцією:

Route::resource('posts', PostController::class);
Метод URL Дія
GET /posts index
GET /posts/create create
POST /posts store
GET /posts/{post} show
GET /posts/{post}/edit edit
PUT/PATCH /posts/{post} update
DELETE /posts/{post} destroy
  • apiResource() - те саме без create/edit (для API).
  • ->only([...]) / ->except([...]) обмежують набір.

Докладніше в документації: Resource-контролери

Rate Limiting обмежує кількість запитів за період. Іменовані обмежувачі визначають через RateLimiter::for() (у Laravel 11+ зазвичай у bootstrap/app.php або AppServiceProvider::boot()):

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

Застосування до маршрутів:

Route::middleware('throttle:api')->group(...);
Route::post('/login', ...)->middleware('throttle:5,1'); // 5 за хвилину

При перевищенні - HTTP 429 із заголовками Retry-After та X-RateLimit-*.

Докладніше в документації: Rate Limiting

Група задає спільні налаштування для кількох маршрутів, щоб не повторювати їх у кожному.

Route::middleware(['auth', 'verified'])
    ->prefix('dashboard')
    ->name('dashboard.')
    ->controller(InvoiceController::class)
    ->group(function () {
        Route::get('/invoices', 'index')->name('invoices.index');       // /dashboard/invoices
        Route::get('/invoices/{invoice}', 'show')->name('invoices.show');
    });

Що можна спільно задати:

  • middleware() - автентифікація, верифікація, обмеження частоти;
  • prefix() - префікс URI;
  • name() - префікс імен маршрутів (крапку в кінці пишуть самі);
  • controller() - спільний контролер, тоді в маршруті лише назва методу;
  • domain() - піддомен, зокрема з параметром: {account}.example.com;
  • scopeBindings() - вкладені моделі шукаються через батьківську.

Вкладені групи об'єднують налаштування: middleware додаються, префікси й імена склеюються.

На практиці групи - головний спосіб не забути auth для нового маршруту: він просто потрапляє в захищену групу.

Докладніше в документації: Групи маршрутів

Маршрути перевіряються в порядку реєстрації, і перемагає перший збіг.

Route::get('/posts/{post}', [PostController::class, 'show']);
Route::get('/posts/create', [PostController::class, 'create']); // ніколи не спрацює

Запит /posts/create збігається з першим маршрутом: create стає значенням {post}, прив'язка моделі не знаходить пост - 404.

Два способи виправити:

  1. Статичні маршрути - вище за параметризовані.
  2. Обмежити параметр, щоб він не ловив чуже:
Route::get('/posts/{post}', [PostController::class, 'show'])->whereNumber('post');
Route::get('/users/{name}', ...)->whereAlpha('name');
Route::get('/orders/{order}', ...)->whereUuid('order');
Route::get('/category/{type}', ...)->whereIn('type', ['news', 'guides']);

Глобальне обмеження для всіх маршрутів з параметром {id}:

// AppServiceProvider::boot()
Route::pattern('id', '[0-9]+');

Route::resource() реєструє create раніше за {post} саме з цієї причини. Проблема виникає, коли маршрути додають вручну в різних місцях файлу.

Докладніше в документації: Обмеження параметрів регулярними виразами

php artisan route:cache компілює всі маршрути в один PHP-файл у bootstrap/cache. Laravel більше не виконує routes/*.php на кожен запит, і реєстрація маршрутів стає майже миттєвою - на застосунках із сотнями маршрутів це помітно.

Коли він ламається чи заважає:

  • Маршрути не оновлюються. Після кешування нові маршрути не з'являються, доки кеш не перебудувати. Тому route:cache (чи optimize) виконують на кожному деплої, а локально не вмикають.
  • Маршрути, що залежать від стану під час реєстрації. Якщо в routes/web.php маршрути генеруються з бази чи з конфігурації, яка змінюється без деплою, кеш «заморозить» їх на момент збирання.
  • Конфлікт імен. Два маршрути з однаковим ім'ям без кешу мовчки перезаписують одне одного, а при кешуванні команда падає з помилкою - це навіть корисно.

Маршрути на замиканнях у сучасних версіях кешуються (їх серіалізують), але звичайна практика - контролери: їх легше тестувати й шукати.

Діагностика: php artisan route:list показує маршрути з кешу, якщо він є; route:clear прибирає кеш.

Докладніше в документації: Кешування маршрутів

Неявна прив'язка за замовчуванням шукає модель за первинним ключем. Є кілька рівнів налаштування - від простого до повного контролю.

Інша колонка для одного маршруту:

Route::get('/posts/{post:slug}', [PostController::class, 'show']);

Інша колонка для моделі завжди:

public function getRouteKeyName(): string
{
    return 'slug';
}

Власна логіка - наприклад, лише опубліковані пости для гостей:

public function resolveRouteBinding($value, $field = null): ?Model
{
    return $this->where($field ?? $this->getRouteKeyName(), $value)
        ->when(! auth()->user()?->isEditor(), fn ($q) => $q->published())
        ->first();
}

null дає 404.

Явна прив'язка в провайдері - коли параметр не відповідає моделі напряму:

Route::bind('user', fn (string $value) => User::where('name', $value)->firstOrFail());

Поряд корисне:

  • ->withTrashed() на маршруті - знаходити м'яко видалені моделі;
  • ->missing(fn () => redirect(...)) - що робити замість 404;
  • scopeBindings() - вкладена модель шукається через батьківську, і чужий ID не пройде.

Важливо: прив'язка моделі - це пошук, а не авторизація. Перевірку прав робить політика.

Докладніше в документації: Налаштування логіки розв'язання