Продвинутые советы по Маршрутизации в Laravel

Маршрутизация Laravel — 8 продвинутых советов

Все мы используем простой синтаксис Route::get() и Route::post(), но в больших проектах это делать сложнее. В этой статье собраны несколько советов для различных ситуаций.

Совет 1. Route::get() ДО Route::resource()

Для Ресурсных контроллеров это одна из самых распространенных ошибок — смотрим пример:

Route::resource('photos', 'PhotoController');
Route::get('photos/popular', 'PhotoController@method');

Второй маршрут будет неточным, знаете почему? Потому, что он будет соответствовать методу show() из Route::resource() — соответственно /photos/{id}, который назначит popular в качестве параметра $id.

Поэтому, если вы хотите добавить любой get/post маршрут, в дополнение к Route::resource(), то размещайте их ДО ресурса. Например так:

Route::get('photos/popular', 'PhotoController@method');
Route::resource('photos', 'PhotoController');

Совет 2. Группа в другой группе

Практически все мы знаем, что можно группировать маршруты с помощью Route::group() и назначать различные мидлвары/префиксы и другие параметры.

А что делать, если нужен определенный набор правил для подгрупп этих групп?

Типичный пример: есть публичные и аутентифицированные маршруты, но в пределах группы с аутентификацией вам нужно отделить администраторов от обычных пользователей.

Можно сделать это так:

// публичные маршруты
Route::get('/', 'HomeController@index');

// Зарегистрированные пользователи - мидлвар «auth»
Route::group(['middleware' => ['auth']], function () {

    // /user/XXX: В дополнении к «auth», эта группа использует мидлвар «simple_users»
    Route::group(['middleware' => ['simple_users'], 'prefix' => 'user'], function () {
        Route::resource('tasks', 'TaskController');
    });

    // /admin/XXX: В этой группе не будет «simple_users», но будут «auth» и «admins»
    Route::group(['middleware' => ['admins'], 'prefix' => 'admin'], function () {
        Route::resource('users', 'UserController');
    });
});

Совет 3. Валидация параметров маршрута — мультиязычный пример

Типичный случай — префикс маршрутов, зависящий от локали, например, fr/blog и en/article/333. Как мы можем гарантировать, что две первые буквы будут использоваться только для переключения языка? А мы можем их проверить прямо в маршруте, с помощью параметра where:

Route::group(['prefix' => '{locale}', 'where' => ['locale' => '[a-zA-Z]{2}']], function () {
    Route::get('/', 'HomeController@index');
    Route::get('article/{id}', 'ArticleController@show');
});

Основная часть — 'where' => ['locale' => '[a-zA-Z]{2}'], где мы используем регулярное выражение для сопоставления только двухбуквенных комбинаций.

Совет 4. Динамическая маршрутизация субдомены

Это есть в официальной документации Laravel, но используется редко, поэтому я решил рассказать об этом.

Если у вас есть динамические субдомены, например, отдельный для каждого пользователя, то он должен быть переменной, верно? Laravel делает это автоматически. Смотрим пример:

Route::domain('{account}.myapp.com')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});

Обратите внимание, что {account} автоматически передается как параметр $account во все методы контроллера, поэтому не забудьте её принять в каждом их них.

Совет 5. Будьте осторожны с неанглийской привязкой модели маршрута

Иногда URL-адреса должны содержать неанглийские слова. Например, у вас есть портал для книг на испанском языке, и вы хотите, чтобы у списка книг был URL /libros, а у отдельной книги — /libros/1, как в обычном ресурсном контроллере.

Но в базе данных все имена должны быть на английском языке, чтобы «магия» Laravel могла работать с единственным и множественным числом, верно?

И, когда вы создаете модель Book с Миграцией и Контроллером, то используете эту команду:

php artisan make:model Book -mcr

Ключ -mcr генерирует Модель и ресурсный Контроллер. В этом контроллере у вас будет:

/**
 * Display the specified resource.
 *
 * @param  \App\Book  $book
 * @return \Illuminate\Http\Response
 */
public function show(Book $book)
{
    // ...
}

Но в вашем routes/web.php у вас сделано так:

Route::resource('libros', 'BookController');

Проблема в том, что это не заработает. Еще большая проблема в том, что не выдаст никакой ошибки, просто $book будет пустым, и вы не поймете, почему.

Согласно официальному описанию контроллера ресурсов, имя переменной должно совпадать с параметром в единственном числе:

// Поэтому вместо
public function show(Book $book)
{
    // ...
}

// нужно сделать
public function show(Book $libro)
{
    // ...
}

Но, честно говоря, в неанглийских проектах я бы рекомендовал вообще не использовать Route::resource и Привязку Модели к Маршруту.
«Магия» слишком непредсказуема. Например, как бы Laravel догадался бы, что единственное число от «libros» — это «libro»?

Совет 6. Маршруты API — из V1 в V2

Представьте, что вы работаете с API-проектом, и вам необходимо выпустить его новую версию. Старые конечные точки останутся по адресу api/[что-то-там], а для новой версии вы будете использовать api/V2/[что-то-там].

Вся логика app/Providers/RouteServiceProvider.php:

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    // ...
}

protected function mapWebRoutes()
{
    Route::middleware('web')
        ->namespace($this->namespace)
        ->group(base_path('routes/web.php'));
}

protected function mapApiRoutes()
{
    Route::prefix('api')
        ->middleware('api')
        ->namespace($this->namespace)
        ->group(base_path('routes/api.php'));
}

Как видите, API-маршруты регистрируются в отдельной функции с префиксом api/.

Итак, если вы хотите создать группу маршрутов V2, то вы можете создать отдельный файл routes/api_v2.php и сделать так:

public function map()
{
    // ... старый функции

    $this->mapApiV2Routes();
}

// И новая функция
protected function mapApiV2Routes()
{
    Route::prefix('api/V2')
        ->middleware('api')
        ->namespace($this->namespace)
        ->group(base_path('routes/api_v2.php'));
}

Таким образом, и старые маршруты не поломаются, и вы легко создадите новый набор маршрутов.

Совет 7. Ограничение скорости — глобальное и для Гостей/Пользователей

Это также есть в официальной документации, но не так подробно.

Во-первых, вы можете ограничить некоторые URL-адреса для вызова максимум 60 раз в минуту, с помощью throttle:60,1.

Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

А вы знали, что можно это сделать отдельно для гостей и залогиненных пользователей?

// максимум 10 запросов в минуту для гостей и 60 для пользователей
Route::middleware('throttle:10|60,1')->group(function () {
    //
});

Также вы можете создать в БД поле users.rate_limit и ограничить запросы для конкретного пользователя:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

Совет 8. Список Маршрутов и Кеширование Маршрутов

Последний совет — как проверить существующие маршруты.

Не все знают, какие именно маршруты скрыты в Route::resource() или в более сложной Route::group.

Но в любой момент вы можете это узнать с помощью artisan-команды:

php artisan route:list

Имейте в виду, что если вы используете кэширование маршрутов, то после каждого изменения маршрутов вам нужно запускать команду:

php artisan route:clear

Автор: Povilas Korop
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.