Laravel 11 路由参数与依赖

上一章我们学习了路由的基础定义。本章将深入探讨路由参数的灵活运用,以及 Laravel 强大的依赖注入功能如何让路由处理变得更加优雅高效。

🎯 路由参数进阶

路由参数是捕获 URL 片段的关键,Laravel 提供了多种方式来约束和定制参数行为。

1. 必选参数与可选参数

必选参数用花括号 {...} 包裹,可选参数则在后边添加 ? 并设置默认值。

// 必选参数
Route::get('/user/{id}', function ($id) {
    return "用户ID: {$id}";
});

// 可选参数(必须设置默认值)
Route::get('/category/{name?}', function ($name = 'all') {
    return "分类: {$name}";
});
2. 参数正则约束

通过 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'); // 字母和数字
3. 全局参数约束

如果希望某个参数在所有路由中都遵循同一约束,可以在 App\Providers\RouteServiceProviderboot 方法中定义全局模式:

public function boot(): void
{
    Route::pattern('id', '[0-9]+');
}

此后,所有路由中的 {id} 参数都会自动应用该正则约束。

4. 获取当前路由参数

在控制器或中间件中,可以通过 Request 实例获取路由参数:

use Illuminate\Http\Request;

Route::get('/post/{id}', function (Request $request) {
    $id = $request->route('id'); // 获取路由参数
    return "文章ID: {$id}";
});

💉 依赖注入在路由中的应用

Laravel 的服务容器会自动解析路由闭包或控制器方法中类型提示的类,这就是依赖注入。

1. 注入请求实例
use Illuminate\Http\Request;

Route::post('/user', function (Request $request) {
    $name = $request->input('name');
    return "收到的姓名: {$name}";
});
2. 注入自定义服务

假设有一个服务类 App\Services\UserService,可以直接在闭包中注入:

use App\Services\UserService;

Route::get('/users/count', function (UserService $userService) {
    return "用户总数: " . $userService->getCount();
});
💡 原理: Laravel 通过反射检查闭包或控制器方法的参数类型,并从容器中解析出对应的实例。这使得代码更易于测试和维护。
3. 依赖注入与路由参数共存

当闭包同时接收依赖和路由参数时,需要将依赖放在前面,路由参数放在后面:

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

📋 常见问题与注意事项

在闭包或控制器方法中,依赖注入的参数(通过类型提示)必须放在路由参数之前。Laravel 会先解析所有类型提示的依赖,然后按顺序匹配路由参数。

可以通过在模型绑定闭包中使用 find 而不是 firstOrFail 来返回 null,然后在路由中处理。或者利用 Laravel 的异常处理机制,抛出自己的异常。

可以,但需要在服务容器中绑定接口到具体实现。例如在 AppServiceProvider$this->app->bind(Interface::class, ConcreteClass::class);

路由级约束会覆盖全局约束。如果同时定义,路由上的 where 会生效。

📝 小结

本章详细介绍了路由参数的约束技巧、依赖注入的灵活运用,以及强大的路由模型绑定功能。掌握这些特性可以显著减少重复代码,让路由定义更加清晰简洁。 下一章我们将学习控制器的组织方式,进一步将业务逻辑从路由文件中分离。