Routing 路由
路由分组 group
在路由中,可以在组内创建组,将某个中间件仅分配给 "父" 组中的某些 URL。
Route::group(['prefix' => 'account', 'as' => 'account.'], function() {
Route::get('login', 'AccountController@login'); // /account/login
Route::get('register', 'AccountController@register'); // /account/register
Route::group(['middleware' => 'auth'], function() {
Route::get('edit', 'AccountController@edit'); // account/edit
});
});
通配符子域
可以通过动态子域名创建路由组,并将其值传递给每个路由。
Route::domain('{username}.workspace.com')->group(function () {
Route::get('user/{id}', function ($username, $id) {
//
});
});
Laravel UI 包 route 方法
如果使用 Laravel UI 包,可能想知道 Auth::routes();
背后的实际路由是什么? 可以检查文件 /vendor/laravel/ui/src/AuthRouteMethods.php
。
public function auth()
{
return function ($options = []) {
// Authentication Routes...
$this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
$this->post('login', 'Auth\LoginController@login');
$this->post('logout', 'Auth\LoginController@logout')->name('logout');
// Registration Routes...
if ($options['register'] ?? true) {
$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController@register');
}
// Password Reset Routes...
if ($options['reset'] ?? true) {
$this->resetPassword();
}
// Password Confirmation Routes...
if ($options['confirm'] ?? class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) {
$this->confirmPassword();
}
// Email Verification Routes...
if ($options['verify'] ?? false) {
$this->emailVerification();
}
};
}
该函数的默认用法很简单:
Auth::routes(); // 默认没有参数
但是可以提供参数来启用或禁用某些路由:
Auth::routes([
'login' => true,
'logout' => true,
'register' => true,
'reset' => true, // 启用重置密码
'confirm' => false, // 启用密码确认
'verify' => false, // 启用邮箱验证
]);
路由模型绑定
可以像 Route::get('api/users/{user}', function (App\Models\User $user) { ... }
之类的路由模型绑定,但不仅可以通过 id 字段。
如果希望 {user}
成为用户名字段,将其放入模型中:
public function getRouteKeyName() {
return 'username';
}
从路由文件快速导航到控制器
这个特性在 Laravel 8 之前是可选的,在 Laravel 8 中成为标准的主要路由语法。
更早时的路由定义:
Route::get('page', 'PageController@action');
可以将 Controller 指定为一个类:
Route::get('page', [\App\Http\Controllers\PageController::class, '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');
});
// Some more routes....
Route::fallback(function() {
return 'Hm, why did you land here somehow?';
});
使用 RegExp 进行路由参数验证
可以直接在路由中验证参数,使用 where
参数。
一个非常常见的情况是通过语言区域设置为路由添加前缀。
use App\Http\Controllers\ArticleController;
use App\Http\Controllers\HomeController;
Route::group([
'prefix' => '{locale}',
'where' => ['locale' => '[a-zA-Z]{2}']
], function () {
Route::get('/', [HomeController::class, 'index']);
Route::get('article/{id}', [ArticleController::class, 'show']);
});
限速
使用场景,发送短信验证码接口每分钟允许请求一次。
// 定义规则,每个用户或IP每分钟允许发送1个请求
// 在 `App\Providers\RouteServiceProvider::configureRateLimiting()` 方法中定义
RateLimiter::for('reset-password', function (Request $request) {
return Limit::perMinute(1)->by(
optional($request->user())->id ?: $request->ip()
);
});
// 在定义路由时使用
Route::get('reset-password', function() {
//
})->middleware(['throttle:reset-password']);
更多官方文档查看这里。
速率限制:全局和针对访客/用户
可以限制某些 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 () {
//
});
});
向路由查询字符串参数
如果将附加参数传递给路由,则在数组中,这些键/值对将自动添加到生成的 URL 的查询字符串中。
Route::get('user/{id}/profile', function ($id) {
//
})->name('profile');
$url = route('profile', ['id' => 1, 'photos' => 'yes']); // Result: /user/1/profile?photos=yes
翻译资源动词
如果使用资源控制器,但想将 URL 动词更改为非英语以用于 SEO 目的,可以使用 App\Providers\RouteServiceProvider
中的 Route::resourceVerbs()
方法对其进行配置。
public function boot()
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
// ...
}
自定义资源路由名称
使用资源控制器时,可以在 routes/web.php
中指定 ->names()
参数,这样浏览器中的 URL 前缀和在整个 Laravel
项目中使用的路由名称前缀可能会有所不同。
Route::resource('p', ProductController::class)->names('products');
所以上面的这段代码会生成像 /p
、/p/{id}
、/p/{id}/edit
等 URL。但可以在代码中通过 route('products.index')
, route('products.create')
等生成 URL。
更具可读性的路由列表
曾经运行过 php artisan route:list
,然后意识到该列表占用了太多空间且难以阅读。
这是解决方案: php artisan route:list --compact
然后它显示 3 列而不是默认的 6 列:仅显示 Method
| URI
| Action
。
+----------+---------------------------------+-------------------------------------------------------------------------+
| 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 |
+----------+---------------------------------+-------------------------------------------------------------------------+
还可以通过 --columns
参数指定所需的确切列:
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 |
+----------+---------------------------------+---------------------+
所有支持的列包含:domain
,method
, uri
, name
, action
, middleware
。
关联关系
如果使用路由模型绑定并认为不能将惰性加载用于关系,但是有一个 belongsTo
关系,使用 ->load()
加载关系。
public function show(Product $product) {
$product->load('category');
//
}
轻松突出显示您的导航栏菜单
使用 Route::is('route-name')
轻松突出显示导航栏菜单:
<ul>
<li @if(Route::is('home')) class="active" @endif>
<a href="/">Home</a>
</li>
<li @if(Route::is('contact-us')) class="active" @endif>
<a href="/contact-us">Contact us</a>
</li>
</ul>
当然 is
方法支持正则匹配,比如 Route::is('posts*')
能匹配 posts.index
和 posts.show
等路由名。
使用 route()
方法生成绝对路径
route('page.show', $page->id); // http://localhost/pages/1
route('page.show', $page->id, false); // /pages/1
覆盖每个模型的路由绑定解析器
可以覆盖每个模型的路由绑定解析器。在此示例中,无法控制 URL
中的 @
符号,因此使用 resolveRouteBinding
方法,能够删除 @
符号并解析模型。
// 路由定义
Route::get('{user}', function (\App\Models\User $user) {
dd($user);
});
// User Model
public function resolveRouteBinding($value, $field = null)
{
$value = str_replace('@', '', $value);
return parent::resolveRouteBinding($value, $field);
}
// 请求 http://localhost/@curder 时,路由参数会被转换成 `curder`
受保护的URL
如果需要公共 URL 但又希望它们受到保护,使用 Laravel 签名 URL 。
class AccountController extends Controller
{
public function destroy(Request $request)
{
$confirmDeleteUrl = URL::signedRoute('confirm-destroy', [
$user => $request->user()
]);
// Send link by email...
}
public function confirmDestroy(Request $request, User $user)
{
if (! $request->hasValidSignature()) {
abort(403);
}
// User confirmed by clikcing on the email
$user->delete();
return redirect()->route('home');
}
}
在中间件方法中使用 Gate
可以在中间件方法中使用您在 App\Providers\AuthServiceProvider
中指定。
// 1. 路由定义
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post ...
})->middleware('can:update-post');
// 2. 授权定义
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
}
优化路由文件
创建一个 /routes/web/
目录,并且在 /routes/web.php
文件中:
foreach(glob(dirname(__FILE__).'/web/*.php', GLOB_NOSORT) as $route_file) {
include $route_file;
}
现在 /routes/web/
中的每个文件都充当路由器文件,可以将路由组织到不同的文件中。
路由资源分组
如果路由有很多资源控制器,可以将它们分组并调用一个 Route::resources()
而不是许多单个 Route::resource()
语句。
Route::resources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
自定义路由绑定
定义 Laravel 应该如何从路由参数中解析它们
// RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{
public function boot()
{
Route::bind('portfolio', function (string $slug) {
return Portfolio::query()
->whereBelongsto(request()->user())
->whereSlug($slug)
->firstOrFail();
});
}
}
Route::get('portfolios/{portfolio}', function (Portfolio $portfolio) {
/*
* The $portfolio will be the result of the query defined in the RouteServiceProvider
*/
})