5. 路由

未匹配的标注

路由

分组中的分组

在路由中,你可以在分组中创建分组,来实现仅仅为父分组中的某些路由分配中间件。

Route::group(['prefix' => 'account', 'as' => 'account.'], function() {
    Route::get('login', 'AccountController@login');
    Route::get('register', 'AccountController@register');

    Route::group(['middleware' => 'auth'], function() {
        Route::get('edit', 'AccountController@edit');
    });
});

通配符子域名

你可以在分组中定义变量,来创建动态子域名分组,然后将这个变量传递给每一个子路由。

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

路由之后?

想知道 Auth::routes() 路由之后是什么?

从 Laravel 7 之后,它被分在一个单独的包中,你可以查阅文件 /vendor/laravel/ui/src/AuthRouteMethods.php

public function auth()
{
    return function ($options = []) {
        // 鉴权路由……
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');
        // 注册路由……
        if ($options['register'] ?? true) {
            $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
            $this->post('register', 'Auth\RegisterController@register');
        }
        // 重置密码路由……
        if ($options['reset'] ?? true) {
            $this->resetPassword();
        }
        // 确认密码路由……
        if ($options['confirm'] ?? class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) {
            $this->confirmPassword();
        }
        // 邮箱验证路由……
        if ($options['verify'] ?? false) {
            $this->emailVerification();
        }
    };
}

Laravel 7 之前,请查阅 /vendor/laravel/framework/src/illuminate/Routing/Router.php

路由模型绑定:你可以定义一个Key

你可以像 Route::get('api/users/{user}', function (App\User $user) { … } 这样来进行路由模型绑定,但不仅仅是 ID 字段,如果你想让 {user}username,你可以把它放在模型中:

public function getRouteKeyName() {
    return 'username';
}

快速从路由导航到控制器

在 Laravel 8 之前,这件事情是可选的。在 Laravel 8 中这将成为路由的标准语法。

你可以将控制器标识为 :

Route::get('page', [\App\Http\Controllers\PageController::class, 'action']);

而不是

Route::get('page', 'PageController@action');

这样,你就可以在PhpStorm中点击 PageController 来跳转到控制器定义,而不是手动去搜索它。

或者你想要让路由的定义更简洁,你可以在路由文件的开始提前引入控制器的类。

use App\Http\Controllers\PageController;

// 然后:
Route::get('page', [PageController::class, 'action']);

备选路由: 当没有匹配到任何路由时

如果你想为未找到的路由指定其它逻辑,而不是直接显示404页面,你可以在路由文件的最后为其创建一个特殊的路由。

Route::group(['middleware' => ['auth'], 'prefix' => 'admin', 'as' => 'admin.'], function () {
    Route::get('/home', 'HomeController@index');
    Route::resource('tasks', 'Admin\TasksController');
});

// 一些其它的路由....
Route::fallback(function() {
    return 'emmm...出错了~';
});

使用正则进行路由参数验证

我们可以在路由中使用 where来直接验证参数。一个典型的例子是,当使用语言区域的参数来作为路由前缀时,像是 fr/blogen/article/333等,这时我们如何来确保这两个首字母没有被用在其他语言呢?

routes/web.php:

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

限流: 全局配置与按用户配置

你可以使用 throttle:60,1 来限制一些 URL 在每分钟内最多被访问 60 次。

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 () {
        //
    });
});

路由中的URL参数

如果你在路由中使用数组传入了其它参数,这些键/值将会自动配对并且带入URL查询参数中。

Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = route('profile', ['id' => 1, 'photos' => 'yes']); // 结果: /user/1/profile?photos=yes

按文件为路由分类

如果你有一组与某些功能相关的路由,你可以将它们放在一个特殊的文件 routes/XXXXX.php 中,然后在 routes/web.php 中使用 include 引入它。

Taylor Otwell 在 Laravel Breeze 中的例子:

routes/auth.php

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

然后,在 routes/auth.php 中:

use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\RegisteredUserController;
// ... 一些控制器

use Illuminate\Support\Facades\Route;

Route::get('/register', [RegisteredUserController::class, 'create'])
                ->middleware('guest')
                ->name('register');

Route::post('/register', [RegisteredUserController::class, 'store'])
                ->middleware('guest');

// ... 另外一些路由

但是,你应该只在路由都各自具有相同的前缀/中间件配置时使用 include() 来引入路由,否则,更好的选择是将他们分类在 app/Providers/RouteServiceProvider 中。

public function boot()
{
    $this->configureRateLimiting();

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

        Route::middleware('web')
            ->namespace($this->namespace)
            ->group(base_path('routes/web.php'));

        // ... 你的路由列在这后边
    });
}

翻译资源中的谓词

当你使用资源控制器,但希望变更 URL 谓词以适应非英语语言环境下的 SEO ,这样在路由中就是 /crear 而非 /create,你可以使用 App\Providers\RouteServiceProvider 中的 Route::resourceVerbs() 配置。

public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);

    // ...
}

自定义资源路由名称

当使用资源路由时,你可以在 routes/web.php 中指定 ->names() 参数,这样一来,在整个 Laravel 项目中,浏览器中的 URL 前缀和路由名称前缀可能会不同。

Route::resource('p', ProductController::class)->names('products');

这行代码将会生成像 /p, /p/{id}, /p/{id}/edit 这样的路由,但是你可以在代码中使用 route('products.index'), route('products.create') 等方式来调用它们。

可读性更强的路由列表

你有没有运行过 php artisan route:list ,然后发现这个列表又长,可读性又很差。

另一个方法是:
php artisan route:list --compact

这样只会输出 3 列,而非 6 列:只展示方法名、 URI 和方法。

+----------+---------------------------------+-------------------------------------------------------------------------+
| Method   | URI                             | Action                                                                  |
+----------+---------------------------------+-------------------------------------------------------------------------+
| GET|HEAD | /                               | Closure                                                                 |
| GET|HEAD | api/user                        | Closure                                                                 |
| POST     | confirm-password                | App\Http\Controllers\Auth\ConfirmablePasswordController@store           |
| GET|HEAD | confirm-password                | App\Http\Controllers\Auth\ConfirmablePasswordController@show            |
| GET|HEAD | dashboard                       | Closure                                                                 |
| POST     | email/verification-notification | App\Http\Controllers\Auth\EmailVerificationNotificationController@store |
| POST     | forgot-password                 | App\Http\Controllers\Auth\PasswordResetLinkController@store             |
| GET|HEAD | forgot-password                 | App\Http\Controllers\Auth\PasswordResetLinkController@create            |
| POST     | login                           | App\Http\Controllers\Auth\AuthenticatedSessionController@store          |
| GET|HEAD | login                           | App\Http\Controllers\Auth\AuthenticatedSessionController@create         |
| POST     | logout                          | App\Http\Controllers\Auth\AuthenticatedSessionController@destroy        |
| POST     | register                        | App\Http\Controllers\Auth\RegisteredUserController@store                |
| GET|HEAD | register                        | App\Http\Controllers\Auth\RegisteredUserController@create               |
| POST     | reset-password                  | App\Http\Controllers\Auth\NewPasswordController@store                   |
| GET|HEAD | reset-password/{token}          | App\Http\Controllers\Auth\NewPasswordController@create                  |
| GET|HEAD | verify-email                    | App\Http\Controllers\Auth\EmailVerificationPromptController@__invoke    |
| GET|HEAD | verify-email/{id}/{hash}        | App\Http\Controllers\Auth\VerifyEmailController@__invoke                |
+----------+---------------------------------+-------------------------------------------------------------------------+

你还可以特别地指定所需要的列:

php artisan route:list --columns=Method,URI,Name

+----------+---------------------------------+---------------------+
| Method   | URI                             | Name                |
+----------+---------------------------------+---------------------+
| GET|HEAD | /                               |                     |
| GET|HEAD | api/user                        |                     |
| POST     | confirm-password                |                     |
| GET|HEAD | confirm-password                | password.confirm    |
| GET|HEAD | dashboard                       | dashboard           |
| POST     | email/verification-notification | verification.send   |
| POST     | forgot-password                 | password.email      |
| GET|HEAD | forgot-password                 | password.request    |
| POST     | login                           |                     |
| GET|HEAD | login                           | login               |
| POST     | logout                          | logout              |
| POST     | register                        |                     |
| GET|HEAD | register                        | register            |
| POST     | reset-password                  | password.update     |
| GET|HEAD | reset-password/{token}          | password.reset      |
| GET|HEAD | verify-email                    | verification.notice |
| GET|HEAD | verify-email/{id}/{hash}        | verification.verify |
+----------+---------------------------------+---------------------+

急切加载

如果你使用了路由模型绑定,并且你认为不会在绑定关系中使用急切加载,请你再想一想。

所以当你用了这样的路由模型绑定

public function show(Product $product) {
    //
}

但是你有一个从属关系,并且不能使用 $product->with('category') 急切加载吗?

但实际上这是可以的,使用 ->load() 来加载关系。

public function show(Product $product) {
    $product->load('category');
    //
}

本地化资源 URI

如果你使用了资源控制器,但是想要将 URL 谓词变为非英语形式的,比如你想要西班牙语的 /crear 而不是 /create ,你可以使用 Route::resourceVerbs() 方法来配置。

public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);
    //
}

资源控制器命名

在资源控制器中,你可以在 routes/web.php 中指定 ->names() 参数,这样 URL前缀与路由前缀可能会不同
.

这样会生成诸如 /p, /p/{id}, /p/{id}/edit 等等,但是你可以这样调用它们:

  • route('products.index)
  • route('products.create)
  • 等等
Route::resource('p', \App\Http\Controllers\ProductController::class)->names('products');

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:6
讨论数量: 0
发起讨论 只看当前版本


暂无话题~