Laravel 路由高级用法
在Laravel 路由一节,我们介绍了Laravel路由的基本的功能,其中包括 路由基本用法 、路由参数 和 命名路由
本节我们继续介绍Laravel路由的一些高级的用法
路由分组
路由分组使我们可以在大量路由之间共享路由属性,例如中间件或命名空间,而无需在每个单独的路由上定义那些属性。 共享属性可以使用Route::group方法的第一个参数指定,该参数是一个数组。
嵌套分组会合并父级分组的属性。 URI前缀中的命名空间定界符和斜杠会在适当的地方自动添加。
Middleware 中间件
要将中间件分配给组内的所有路由,可以在定义组之前使用middleware
方法。 中间件按照它们在数组中列出的顺序执行:
Route::middleware(['firstMiddleware', 'secondMiddleware'])->group(function () {
Route::get('/', function () {
// Uses first & second Middleware
});
Route::get('user/profile', function () {
// Uses first & second Middleware
});
});
Namespace 命名空间
路由组的另一个常见用例是使用命名空间方法将相同的PHP命名空间分配给一组控制器:
Route::namespace('Admin')->group(function () {
// Controllers Within The "App\Http\Controllers\Admin" Namespace
});
请记住,默认情况下,RouteServiceProvider
将路由文件包含在命名空间组中,从而无需指定完整的App\Http\Controllers\yourNamespace
即可注册控制器路由。 因此,我们只需要指定位于App\Http\Controllers
命名空间之后的部分。
子域路由
路由分组也可以用于处理子域路由。 可以为子域分配路由参数,就像路由URI一样,允许您捕获子域的一部分以在路由或控制器中使用。 可以通过在定义组之前调用domain方法来指定子域:
Route::domain('{account}.example.com')->group(function () {
Route::get('user/{id}', function ($account, $id) {
//
});
});
为了确保子域路由可访问,应该在注册根域路由之前注册子域路由。 这样可以防止根域路由覆盖具有相同URI路径的子域路由。
路由前缀
可以使用前缀方法为组中的每个路由添加给定的URI前缀。 例如,在组中为所有路由URI加上admin前缀:
Route::prefix('admin')->group(function () {
Route::get('/users', function () {
// Matches The "/admin/users" URL
});
});
路由名称前缀
name
方法可用于在组中的每个路由名称前添加给定字符串。 例如,我们可能想在所有分组路由的名称前加上admin。 给定的字符串将完全按照指定的前缀添加到路由名称上,因此我们将确保 在前缀字符串的后面添加点.
:
Route::name('admin.')->group(function () {
Route::get('/users', function () {
// Route assigned name "admin.users"...
})->name('users');
});
路由 和 Model 绑定
在将模型ID注入到路由或控制器时,我们通常会查询数据库以检索与该ID对应的模型。 Laravel 路由模型绑定提供了一种方便的方法,可以将模型实例直接自动注入到路由中。 例如,可以注入与给定ID匹配的整个User模型实例,而不是注入用户的ID。
隐式绑定
Laravel自动解析在路由或控制器中定义的Eloquent模型,这些变量名称前面有类型限定,与路由段名称匹配。 例如:
use App\Models\User;
Route::get('/users/{user}', function (User $user) {
return $user->email;
});
由于$user
变量的类型提示为App\Models\User
Eloquent模型,并且变量名称与{user} URI段匹配,因此Laravel将自动注入具有与请求URI中的对应ID匹配的模型实例。 如果在数据库中找不到匹配的模型实例,则会自动生成404 HTTP响应。
当然,使用控制器方法时,也是可以使用隐式绑定的。 再次注意,URI段中的{user}
与包含App\Models\User
类型提示的控制器中的$user
变量匹配:
use App\Http\Controllers\UserController;
use App\Models\User;
// Route definition...
Route::get('/users/{user}', [UserController::class, 'show']);
// Controller method definition...
public function show(User $user)
{
return view('user.profile', ['user' => $user]);
}
自定义Key
有时,我们可能希望使用id以外的列来解析Eloquent模型。 为此,我们可以在route参数定义中指定列:
use App\Models\Post;
Route::get('/posts/{post:slug}', function (Post $post) {
return $post;
});
如果希望始终使用id以外的数据库列来进行模型绑定,则可以在Eloquent模型上重写getRouteKeyName
方法:
/**
* Get the route key for the model.
*
* @return string
*/
public function getRouteKeyName()
{
return 'slug';
}
自定义key & 范围
当在单个路由定义中隐式绑定多个Eloquent模型时,您可能希望将第二个Eloquent模型的范围必须是先前Eloquent模型的子范围。 例如,考虑以下路由定义,该路由定义通过slug检索特定用户的博客文章:
use App\Models\Post;
use App\Models\User;
Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
return $post;
});
当使用自定义键隐式绑定作为嵌套的路由参数时,Laravel将自动确定范围,使用一些特定的规则来猜测其父级上的关系名称,从而由其父级检索嵌套模型。 在这种情况下,将假定用户模型具有一个名为post(路由参数名称的复数形式)的关系,该关系可用于检索Post模型。
自定义缺省的Model行为
通常,如果未找到隐式绑定模型,则会生成404 HTTP响应。 但是,我们可以通过在定义路由时调用缺省的方法来自定义此行为。 如果找不到隐式绑定的模型,则缺省的方法接受将被调用的闭包:
use App\Http\Controllers\LocationsController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
->name('locations.view')
->missing(function (Request $request) {
return Redirect::route('locations.index');
});
显示绑定
我们不需要使用Laravel的基于约定的隐式模型解析来使用模型绑定。 我们还可以显式定义路由的参数与模型对应。 要注册显式绑定,请使用路由器的model
方法为给定的参数指定一个类。 应该在RouteServiceProvider
类的启动方法的开头定义显式模型绑定:
use App\Models\User;
use Illuminate\Support\Facades\Route;
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
Route::model('user', User::class);
// ...
}
接下来,定义一个包含{user}
参数的路由。
use App\Models\User;
Route::get('/users/{user}', function (User $user) {
//
});
由于我们已将所有{user}
参数绑定到App\Models\User
模型,因此该类的实例将被注入到路由中。 因此,例如,对users/1
的请求将从ID为1的数据库获取User模型并注入。
如果在数据库中找不到匹配的模型实例,则会自动生成404 HTTP响应。
自定义解析逻辑
如果希望定义自己的模型绑定解析逻辑,则可以使用Route::bind
方法。 传递给bind方法的闭包将接收URI段的值,并返回应注入到路由中的类的实例。 同样,此自定义应在应用程序的RouteServiceProvider
的boot
方法中进行:
use App\Models\User;
use Illuminate\Support\Facades\Route;
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
Route::bind('user', function ($value) {
return User::where('name', $value)->firstOrFail();
});
// ...
}
或者,可以在Eloquent模型上覆盖resolveRouteBinding
方法。 此方法将接收URI段的值,并返回应注入到路由中的类的实例:
/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
return $this->where('name', $value)->firstOrFail();
}
如果路由利用隐式绑定作用域,则resolveChildRouteBinding
方法将用于解析父模型的子绑定:
/**
* Retrieve the child model for a bound value.
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveChildRouteBinding($childType, $value, $field)
{
return parent::resolveChildRouteBinding($childType, $value, $field);
}
后备路由
使用Route::fallback
方法,可以定义一个路由,当没有其他路由与传入请求匹配时将执行该路由。 通常,未处理的请求将通过应用程序的异常处理程序自动呈现“ 404”页面。 但是,由于通常会在route/web.php
文件中定义后备路由,因此Web中间件组中的所有中间件都将应用于该路由。 我们可以根据需要随意向此路由添加其他中间件:
Route::fallback(function () {
//
});
后备路由应始终是应用程序注册的最后一条路由。
访问频率限制
定义频率限制
Laravel包括功能强大且可自定义的访问频率限制服务,我们可以利用这些服务来限制给定路由或一组路由的流量。 首先,应该定义满足应用程序需求的速率限制器配置。 通常,这应该在应用程序的App\Providers\RouteServiceProvider
类的configureRateLimiting
方法中完成。
访问频率限制器是使用RateLimiter
Facade 的for
方法定义的。 for方法接受一个频率限制器名称和一个闭包,该闭包将返回限制配置,该配置应适用于分配给该频率限制器的路由。 限制配置是Illuminate\Cache\RateLimiting\Limit
类的实例。 此类包含“生成器”方法,以便我们可以快速定义限制。 频率限制器名称可以是我们希望的任何字符串:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
/**
* Configure the rate limiters for the application.
*
* @return void
*/
protected function configureRateLimiting()
{
RateLimiter::for('global', function (Request $request) {
return Limit::perMinute(1000);
});
}
如果传入的请求超出指定的频率限制,Laravel将自动返回带有429 HTTP状态代码的响应。 如果想定义自己的应由频率限制返回的响应,则可以使用response方法:
RateLimiter::for('global', function (Request $request) {
return Limit::perMinute(1000)->response(function () {
return response('Custom response...', 429);
});
});
由于频率限制器回调函数收到传入的HTTP请求实例,因此可以根据传入的请求或经过身份验证的用户动态构建适当的频率限制:
RateLimiter::for('uploads', function (Request $request) {
return $request->user()->vipCustomer()
? Limit::none()
: Limit::perMinute(100);
});
细化频率限制
有时我们可能希望将速率限制按任意值分段。 例如,可能希望允许用户每个IP地址每分钟100次访问给定的路由。 为此,我们可以在建立可以使用by
方法进行频率限制:
RateLimiter::for('uploads', function (Request $request) {
return $request->user()->vipCustomer()
? Limit::none()
: Limit::perMinute(100)->by($request->ip());
});
为了用另一个示例说明此功能,我们可以将访客的访问权限限制为每个经过身份验证的用户ID每分钟100次或每IP地址每分钟10次:
RateLimiter::for('uploads', function (Request $request) {
return $request->user()
? Limit::perMinute(100)->by($request->user()->id)
: Limit::perMinute(10)->by($request->ip());
});
多频率限制
如果需要,我们可以返回给定频率限制器配置的频率限制数组。 将根据路由中每个频率限制在数组中的放置顺序对其进行检测:
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(500),
Limit::perMinute(3)->by($request->input('email')),
];
});
将频率限制附加在路由上
可以使用throttle
中间件将频率限制器附加到路由或路由组。 throttle
中间件接受我们希望分配给路由的频率限制器的名称:
Route::middleware(['throttle:uploads'])->group(function () {
Route::post('/audio', function () {
//
});
Route::post('/video', function () {
//
});
});
使用Redis节流
通常,throttle
中间件映射到Illuminate\Routing\Middleware\ThrottleRequests
类。 此映射在应用程序的HTTP内核(App\Http\Kernel)中定义。 但是,如果我们将Redis用作应用程序的缓存驱动程序,则可能希望更改此映射从而使其可以使用Illuminate\Routing\Middleware\ThrottleRequestsWithRedis
类。 此类在使用Redis管理频率限制方面更为有效:
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
表单隐藏方法
HTML表单不支持PUT,PATCH或DELETE操作。 因此,在定义从HTML表单调用的PUT,PATCH或DELETE路由时,我们需要向表单添加一个隐藏的_method
字段。 与_method
字段一起发送的值将用作HTTP请求方法:
<form action="/example" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
为了方便起见,可以使用@method Blade
指令生成_method
输入字段:
<form action="/example" method="POST">
@method('PUT')
@csrf
</form>
获取当前路由
我们可以在Route Facade 上使用current,currentRouteName和currentRouteAction方法来访问有关处理传入请求的路由的信息:
use Illuminate\Support\Facades\Route;
$route = Route::current(); // Illuminate\Routing\Route
$name = Route::currentRouteName(); // string
$action = Route::currentRouteAction(); // string
跨域资源共享(CORS)
Laravel可以使用我们配置的值自动响应CORS OPTIONS HTTP请求。 可以在应用程序的config/cors.php
配置文件中配置所有CORS设置。 OPTIONS请求将由默认包含在全局中间件堆栈中的HandleCors
中间件自动处理。 我们的全局中间件堆栈位于应用程序的HTTP内核(App\Http\Kernel
)中。
路由缓存
将应用程序部署到生产环境时,应该利用Laravel的路由缓存。 使用路由缓存将大大减少注册所有应用程序路由所需的时间。 要生成路由缓存,请执行Artisan 命令 route:cache
:
$ php artisan route:cache
运行此命令后,缓存的路由文件将在每个请求中加载。 请记住,如果添加任何新路由,则将需要生成新的路由缓存。 因此,我们仅仅应在项目部署期间运行route:cache
命令。
我们可以使用route:clear
命令清除路由缓存:
$ php artisan route:clear