Структурирование маршрутов в крупных проектах на Laravel

Структурирование маршрутов в крупных проектах

Представьте себе проект Laravel с сотней маршрутов, отдельными секциями для гостей, пользователей, администраторов и т.д. Вы действительно хотите хранить все это в одном файле? Как их сгруппировать, как добавить префиксы в URL? Давайте посмотрим, что мы можем сделать.

1. Разделите WEB и API маршруты

Это легко, так как Laravel делает это по умолчанию. Есть два файла:

  • routes/web.php
  • routes/api.php

Поэтому, если ваш проект имеет и веб-страницы, и API (что в наши дни становится все более распространенным), пожалуйста, поместите маршруты API в этот отдельный файл.

Например, если у вас есть страница /users и конечная точка /api/users/, то разделение их на отдельные файлы поможет не запутаться в одинаковых именах.

Тем не менее, я недавно видел нелогичный пример в официальном проекте Laravel. Horizon у Тейлора имеет только API-маршруты, но он не использует отдельный файл, а поместил его в route/web.php:
Маршруты в Ларавел

Это доказательство того, что структурирование в Laravel делается по личным предпочтениями и не существует 100% стандарта, даже от самого Тейлора.

2. Структурируйте файл routs/web.php в Группы

Это также идёт в «базовом» Laravel — группировка маршрутов. Пример из официальной документации Laravel:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // использует мидлвары first и second
    });

    Route::get('user/profile', function () {
        // использует мидлвары first и second
    });
});

Основное использование — это скрытие разных групп под разными мидлварами. Например, вы хотите, чтобы одна группа была ограничена дефолтным auth мидлваром, а другая — отдельным админским и т.д.

При этом вы можете использовать имена и префиксы групп маршрутов. Опять же, несколько примеров из официальной документации:

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // Соответствует адресу "/admin/users"
    });
});

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // Маршруту присваивается имя "admin.users"...
    })->name('users');
});

Кроме того, если вы хотите добавить мидлвар+имя+префикс в одну группу, то сделайте их массивом, чтобы удобнее было читать:

// Вместо такой цепочки
Route::name('admin.')->prefix('admin')->middleware('admin')->group(function () {
    // ...
});

// Вы можете использовать массив
Route::group([
    'name' => 'admin.', 
    'prefix' => 'admin', 
    'middleware' => 'auth'
], function () {
    // ...
});

Давайте соединим всё это в реальном примере:

  • Группа «Гость» с адресами /front/XXXXX и без мидлвара;
  • Группа «Пользователь» с адресами /user/XXXXX и auth-мидлваром;
  • Группа «Администратор» с адресами /admin/XXXXX и отдельным admin-мидлваром.

Группируем всё это в файле routs/web.php:

Route::group([
    'name' => 'admin.',
    'prefix' => 'admin',
    'middleware' => 'admin'
], function () {

    // адрес: /admin/users
    // имя маршрута: admin.users
    Route::get('users', function () {
        return 'Admin: user list';
    })->name('users');

});

Route::group([
    'name' => 'user.',
    'prefix' => 'user',
    'middleware' => 'auth'
], function () {

    // адрес: /user/profile
    // имя маршрута: user.profile
    Route::get('profile', function () {
        return 'User profile';
    })->name('profile');

});

Route::group([
    'name' => 'front.',
    'prefix' => 'front'
], function () {

    // без мидлвара
    // адрес: /front/about-us
    // имя маршрута: front.about
    Route::get('about-us', function () {
        return 'About us page';
    })->name('about');

});

3. Группируйте контроллеры с пространствами имен

В выше приведенном примере мы не использовали контроллеры, а просто возвращали статический текст. Давайте добавим контроллеры с еще одним «поворотом» — мы будем структурировать их по папкам с их собственными пространствами имен, например так:
Маршруты контроллеров в Ларавел

И тогда мы можем использовать их в файле маршрутов:

Route::group([
    'name' => 'front.',
    'prefix' => 'front'
], function () {
    Route::get('about-us', 'Front\AboutController@index')->name('about');
});

Но что, если в этой группе будет много контроллеров? Надо ли постоянно добавлять Front\SomeController? Конечно, нет. Вы просто можете указать пространство имен в качестве одного из параметров.

Route::group([
    'name' => 'front.',
    'prefix' => 'front',
    'namespace' => 'Front',
], function () {
    Route::get('about-us', 'AboutController@index')->name('about');
    Route::get('contact', 'ContactController@index')->name('contact');
});

4. Группа в группе

Вышеуказанная ситуация с тремя группами упрощена, реальные проекты имеют немного другую структуру — из двух групп: front и auth. А внутри auth есть подгруппы: user и admin . Для этого мы можем создать подгруппы в routes/web.php и назначить различные мидлвары/префиксы и т.д.

Route::group([
    'middleware' => 'auth',
], function() {

    Route::group([
        'name' => 'admin.',
        'prefix' => 'admin',
        'middleware' => 'admin'
    ], function () {

        // адрес: /admin/users
        // имя маршрута: admin.users
        Route::get('users', 'UserController@index')->name('users');

    });

    Route::group([
        'name' => 'user.',
        'prefix' => 'user',
    ], function () {

        // адрес: /user/profile
        // имя маршрута: user.profile
        Route::get('profile', 'ProfileController@index')->name('profile');

    });

});

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

Route::group(['middleware' => 'language'], function () {
    Route::group(['middleware' => 'auth'], function () {
        Route::group(['prefix' => 'uploads'], function () {
            Route::get('{id}', 'Common\Uploads@get');
            Route::get('{id}/show', 'Common\Uploads@show');
            Route::get('{id}/download', 'Common\Uploads@download');
        });

        Route::group(['middleware' => 'permission:read-admin-panel'], function () {
            Route::group(['prefix' => 'wizard'], function () {
                Route::get('/', 'Wizard\Companies@edit')->name('wizard.index');
        
        // ...

Еще один пример взят из другого популярного Laravel CRM под названием Monica:

Route::middleware(['auth', 'verified', 'mfa'])->group(function () {
    Route::name('dashboard.')->group(function () {
        Route::get('/dashboard', 'DashboardController@index')->name('index');
        Route::get('/dashboard/calls', 'DashboardController@calls');
        Route::get('/dashboard/notes', 'DashboardController@notes');
        Route::get('/dashboard/debts', 'DashboardController@debts');
        Route::get('/dashboard/tasks', 'DashboardController@tasks');
        Route::post('/dashboard/setTab', 'DashboardController@setTab');
    });

5. Глобальные настройки в RouteServiceProvider

Есть файл, который настраивает все маршруты — app/Providers/RouteServiceProvider.php. У него есть метод map(), в котором он связывает оба файла маршрутов — web и API:

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, так как их настройки обычно одинаковы, например:

protected function mapApiRoutes()
{
    Route::group([
        'middleware' => ['api'],
        'namespace' => $this->namespace,
        'prefix' => 'api/v1',
    ], function ($router) {
        require base_path('routes/api.php');
    });
}

Метод выше добавит префикс «api/v1/» для всех API адресов.

6. Группировка по файлам — стоит ли это того?

Если у вас огромное количество маршрутов и вы хотите еще больше сгруппировать их в отдельные файлы, то вы можете использовать файл, который упоминался в предыдущем разделе — app/Providers/RouteServiceProvider.php.

Если вы присмотритесь к методу map(), вы увидите закомментированное место в конце:

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

    $this->mapWebRoutes();

    //
}

Вы можете интерпретировать это как «приглашение» добавить еще файлы, если захотите. Таким образом, вы можете создать внутри этого файла еще один метод, например mapAdminRoutes(), а затем добавить его в метод map(), и ваш файл будет зарегистрирован и автоматически загружен.

Но я лично не вижу большого преимущества в этом подходе, и не видел, чтобы так часто делалось. Это даёт немного большее разделение маршрутов, но иногда вы теряетесь в этих файлах и не помните, где искать конкретный маршрут.

7. Нахождение точного маршрута с помощью команды Artisan route:list

Говоря о громоздкой маршрутизации и сложностью ориентирования в ней, у нас есть одна artisan команда, которая поможет найти нужный маршрут.

Вы все, вероятно, знаете, что php artisan route:list показывает все маршруты в проекте:
Artisan Route List

Но знаете ли вы, что можно фильтровать вывод, чтобы найти именно то, что нужно? Просто добавьте –method, или –name, или –path с параметрами.

Фильтрование по методу — GET, POST и т.д.
Artisan Route List Method

Фильтрование по имени или части адреса:
Artisan Route List Name

Это всё, что я мог рассказать о группировке маршрутов в крупных проектах.

 

Автор: Povilas Korop
Перевод: Demiurge Ash