上一章我们学习了路由的基础定义。本章将深入探讨路由参数的灵活运用,以及 Laravel 强大的依赖注入功能如何让路由处理变得更加优雅高效。
路由参数是捕获 URL 片段的关键,Laravel 提供了多种方式来约束和定制参数行为。
必选参数用花括号 {...} 包裹,可选参数则在后边添加 ? 并设置默认值。
// 必选参数
Route::get('/user/{id}', function ($id) {
return "用户ID: {$id}";
});
// 可选参数(必须设置默认值)
Route::get('/category/{name?}', function ($name = 'all') {
return "分类: {$name}";
});
通过 where 方法限制参数格式,确保参数符合预期。
Route::get('/user/{id}', function ($id) {
return "用户ID: {$id}";
})->where('id', '[0-9]+'); // 只允许数字
Route::get('/post/{id}/{slug}', function ($id, $slug) {
return "文章ID: {$id}, 别名: {$slug}";
})->where(['id' => '[0-9]+', 'slug' => '[a-z0-9-]+']); // 同时约束多个
Laravel 内置了一些常用约束辅助方法:
Route::get('/user/{id}', function ($id) {
//
})->whereNumber('id'); // 等同于 ->where('id', '[0-9]+')
Route::get('/category/{name}', function ($name) {
//
})->whereAlpha('name'); // 仅字母
Route::get('/post/{slug}', function ($slug) {
//
})->whereAlphaNumeric('slug'); // 字母和数字
如果希望某个参数在所有路由中都遵循同一约束,可以在 App\Providers\RouteServiceProvider 的 boot 方法中定义全局模式:
public function boot(): void
{
Route::pattern('id', '[0-9]+');
}
此后,所有路由中的 {id} 参数都会自动应用该正则约束。
在控制器或中间件中,可以通过 Request 实例获取路由参数:
use Illuminate\Http\Request;
Route::get('/post/{id}', function (Request $request) {
$id = $request->route('id'); // 获取路由参数
return "文章ID: {$id}";
});
Laravel 的服务容器会自动解析路由闭包或控制器方法中类型提示的类,这就是依赖注入。
use Illuminate\Http\Request;
Route::post('/user', function (Request $request) {
$name = $request->input('name');
return "收到的姓名: {$name}";
});
假设有一个服务类 App\Services\UserService,可以直接在闭包中注入:
use App\Services\UserService;
Route::get('/users/count', function (UserService $userService) {
return "用户总数: " . $userService->getCount();
});
当闭包同时接收依赖和路由参数时,需要将依赖放在前面,路由参数放在后面:
use Illuminate\Http\Request;
Route::put('/user/{id}', function (Request $request, $id) {
// $request 是依赖注入,$id 是路由参数
return "更新用户ID: {$id},请求数据: " . json_encode($request->all());
});
路由模型绑定允许直接将路由参数(如用户ID)解析为对应的模型实例,极大地简化代码。
只要路由参数名与控制器/闭包中的模型变量名一致(或使用类型提示),Laravel 会自动查询对应模型。
use App\Models\User;
Route::get('/user/{user}', function (User $user) {
// $user 就是 ID 对应的 User 模型实例,如果找不到会自动返回 404
return $user;
});
上述路由会自动通过 {user} 的值(如 1)在 users 表中查询 id = 1 的记录,并注入 $user 变量。
默认使用 id 作为查询字段,但可以通过覆盖模型的 getRouteKeyName 方法修改:
// 在 User 模型中
public function getRouteKeyName(): string
{
return 'slug'; // 使用 slug 字段代替 id
}
// 路由依然不变
Route::get('/user/{user}', function (User $user) {
return $user; // 现在会通过 slug 查询
});
默认情况下,隐式绑定会包含已软删除的模型。如果要排除软删除的记录,可以在模型中使用 withTrashed 方法:
Route::get('/user/{user}', function (User $user) {
return $user;
})->withTrashed(); // 允许访问软删除的用户
如果不希望包含软删除,无需任何操作,默认排除。
有时需要更精细的控制,例如使用非标准字段查询,或自定义解析逻辑。可以在 RouteServiceProvider 中显式绑定:
use App\Models\Post;
use Illuminate\Support\Facades\Route;
public function boot(): void
{
Route::bind('post', function ($value) {
return Post::where('slug', $value)->firstOrFail();
});
}
然后在路由中使用:Route::get('/post/{post}', function (Post $post) { ... });。
也可以让模型自己负责解析:
// 在 RouteServiceProvider 中
Route::model('article', Article::class);
// 然后在 Article 模型中实现 resolveRouteBinding 方法
public function resolveRouteBinding($value, $field = null)
{
return $this->where('slug', $value)->firstOrFail();
}
在实际开发中,推荐将路由逻辑放在控制器中,这样能更好地组织代码。以下是一个控制器示例:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use App\Services\UserService;
class UserController extends Controller
{
public function show(User $user) // 模型绑定
{
return view('users.show', compact('user'));
}
public function update(Request $request, User $user) // 同时注入 Request 和模型
{
$user->update($request->only('name', 'email'));
return redirect()->route('users.show', $user);
}
public function count(UserService $service) // 注入服务
{
return $service->getCount();
}
}
find 而不是 firstOrFail 来返回 null,然后在路由中处理。或者利用 Laravel 的异常处理机制,抛出自己的异常。
AppServiceProvider 中 $this->app->bind(Interface::class, ConcreteClass::class);。
where 会生效。
本章详细介绍了路由参数的约束技巧、依赖注入的灵活运用,以及强大的路由模型绑定功能。掌握这些特性可以显著减少重复代码,让路由定义更加清晰简洁。 下一章我们将学习控制器的组织方式,进一步将业务逻辑从路由文件中分离。