路由模型绑定 #

一、概述 #

1.1 什么是路由模型绑定 #

路由模型绑定允许你自动将路由参数解析为对应的Eloquent模型实例,无需手动查询数据库。

text
传统方式
┌─────────────┐
│  /users/1   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 控制器方法   │
│ $id = 1     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 手动查询    │
│ User::find($id)│
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 返回用户    │
└─────────────┘

模型绑定方式
┌─────────────┐
│  /users/1   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 自动解析    │
│ User模型    │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 控制器方法   │
│ $user = User实例│
└─────────────┘

1.2 绑定类型 #

类型 说明
隐式绑定 自动根据类型提示绑定
显式绑定 手动定义绑定规则

二、隐式绑定 #

2.1 基本用法 #

php
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get('/users/{user}', function (User $user) {
    return $user;
});

访问 /users/1 时,Laravel自动查询 id = 1 的用户。

2.2 在控制器中使用 #

php
// 路由定义
Route::get('/users/{user}', [UserController::class, 'show']);

// 控制器
class UserController extends Controller
{
    public function show(User $user)
    {
        return view('users.show', compact('user'));
    }
}

2.3 自定义键名 #

默认使用 id 字段,可以指定其他字段:

php
Route::get('/users/{user:slug}', function (User $user) {
    return $user;
});

访问 /users/john-doe 时,使用 slug 字段查询。

2.4 自定义键名与参数名不同 #

php
// 参数名: user_slug
// 键名: slug
Route::get('/users/{user_slug}', function (User $user) {
    return $user;
})->scopeBindings();

三、显式绑定 #

3.1 定义显式绑定 #

RouteServiceProvider 中定义:

php
// app/Providers/RouteServiceProvider.php

public function boot()
{
    parent::boot();
    
    Route::bind('user', function ($value) {
        return User::where('slug', $value)->firstOrFail();
    });
}

3.2 使用显式绑定 #

php
Route::get('/users/{user}', function (User $user) {
    return $user;
});

3.3 绑定到模型 #

php
public function boot()
{
    parent::boot();
    
    Route::model('user', User::class);
}

四、软删除模型 #

4.1 包含软删除 #

默认情况下,软删除的模型不会被解析:

php
// 只返回未软删除的模型
Route::get('/users/{user}', function (User $user) {
    return $user;
});

要包含软删除的模型:

php
Route::get('/users/{user}', function (User $user) {
    return $user;
})->withTrashed();

4.2 仅软删除 #

php
Route::get('/users/{user}', function (User $user) {
    return $user;
})->onlyTrashed();

五、自定义异常 #

5.1 自定义未找到异常 #

php
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get('/users/{user}', function (User $user) {
    return $user;
})->missing(function () {
    return redirect()->route('users.index');
});

5.2 全局自定义 #

php
// app/Providers/RouteServiceProvider.php

public function boot()
{
    parent::boot();
    
    Route::bind('user', function ($value) {
        return User::where('slug', $value)->first() ?? abort(404);
    });
}

六、嵌套绑定 #

6.1 父子关系绑定 #

php
use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();

6.2 作用域绑定 #

确保子模型属于父模型:

php
// 启用作用域绑定
Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();

// 或在路由组中启用
Route::scopeBindings()->group(function () {
    Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
        return $post;
    });
});

6.3 嵌套绑定示例 #

php
// models
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// 路由
Route::scopeBindings()->group(function () {
    Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
        // $post 自动验证属于 $user
        return $post;
    });
});

七、枚举绑定 #

7.1 枚举类定义 #

php
enum Category: string
{
    case Fruits = 'fruits';
    case People = 'people';
}

7.2 路由中使用枚举 #

php
use App\Enums\Category;

Route::get('/categories/{category}', function (Category $category) {
    return $category->value;
});

访问 /categories/fruits 返回 fruits

八、绑定接口 #

8.1 定义接口 #

php
interface OrderRepositoryInterface
{
    public function find($id);
}

8.2 实现接口 #

php
class CacheOrderRepository implements OrderRepositoryInterface
{
    public function find($id)
    {
        return Cache::get("order.{$id}");
    }
}

8.3 绑定接口 #

php
// app/Providers/RouteServiceProvider.php

public function boot()
{
    parent::boot();
    
    Route::bind('order', function ($id) {
        return app(OrderRepositoryInterface::class)->find($id);
    });
}

九、自定义解析逻辑 #

9.1 使用resolve方法 #

php
Route::bind('user', function ($value) {
    // 自定义解析逻辑
    if (is_numeric($value)) {
        return User::findOrFail($value);
    }
    
    return User::where('slug', $value)->firstOrFail();
});

9.2 带缓存的解析 #

php
Route::bind('user', function ($value) {
    return Cache::remember("user.{$value}", 3600, function () use ($value) {
        return User::where('slug', $value)->firstOrFail();
    });
});

9.3 多字段解析 #

php
Route::bind('user', function ($value) {
    return User::where('id', $value)
        ->orWhere('slug', $value)
        ->orWhere('email', $value)
        ->firstOrFail();
});

十、性能优化 #

10.1 预加载关联 #

php
Route::get('/users/{user}', function (User $user) {
    $user->load('posts', 'comments');
    return $user;
});

10.2 缓存模型 #

php
Route::bind('user', function ($value) {
    return Cache::remember("user.{$value}", 60, function () use ($value) {
        return User::findOrFail($value);
    });
});

10.3 选择性字段 #

php
Route::get('/users/{user}', function (User $user) {
    return $user->only(['id', 'name', 'email']);
});

十一、实战示例 #

11.1 博客文章路由 #

php
// 路由定义
Route::get('/posts/{post:slug}', [PostController::class, 'show']);

// 控制器
public function show(Post $post)
{
    $post->load(['author', 'comments.user', 'tags']);
    return view('posts.show', compact('post'));
}

11.2 用户资料路由 #

php
// 使用用户名访问
Route::get('/@{user:username}', [ProfileController::class, 'show']);

// 控制器
public function show(User $user)
{
    return view('profiles.show', compact('user'));
}

// 访问 /@john

11.3 多语言路由 #

php
// 语言枚举
enum Language: string
{
    case En = 'en';
    case Zh = 'zh';
    case Ja = 'ja';
}

// 路由
Route::get('/{language}/posts/{post}', function (Language $language, Post $post) {
    app()->setLocale($language->value);
    return view('posts.show', compact('post'));
});

11.4 API资源路由 #

php
Route::apiResource('users.posts', PostController::class)
    ->scoped(['post' => 'slug']);

// GET /users/1/posts/my-first-post
// 自动验证 post 属于 user

十二、常见问题 #

12.1 参数名与变量名不匹配 #

php
// 错误:参数名是 user_id
Route::get('/users/{user_id}', function (User $user) {
    return $user;
});

// 正确:参数名与变量名一致
Route::get('/users/{user}', function (User $user) {
    return $user;
});

12.2 自定义键名注意事项 #

php
// 正确:明确指定键名
Route::get('/users/{user:slug}', function (User $user) {
    return $user;
});

// 错误:模型没有 slug 字段

12.3 作用域绑定失败 #

php
// 确保模型定义了关联关系
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

// 确保启用作用域绑定
Route::scopeBindings()->group(function () {
    Route::get('/users/{user}/posts/{post}', ...);
});

十三、总结 #

13.1 核心要点 #

要点 说明
隐式绑定 类型提示自动解析
显式绑定 手动定义解析规则
自定义键 指定查询字段
作用域绑定 验证父子关系
软删除 withTrashed/onlyTrashed
枚举绑定 PHP 8.1+ 枚举支持

13.2 最佳实践 #

  1. 使用语义化的参数名
  2. 合理使用作用域绑定
  3. 对频繁访问的模型使用缓存
  4. 预加载关联关系

13.3 下一步 #

掌握了路由模型绑定后,让我们继续学习 控制器基础,深入了解Laravel控制器!

最后更新:2026-03-28