Laravel 13 路由高级特性

路由是 Laravel 应用的入口点,Laravel 13 提供了丰富的高级路由特性,帮助开发者构建灵活、强大的 URL 结构。

路由模型绑定

隐式绑定

1
2
3
4
5
6
7
8
9
10
use App\Models\User;
use App\Models\Post;

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

Route::get('/posts/{post}', function (Post $post) {
return $post;
});

自定义键名

1
2
3
4
5
6
7
Route::get('/users/{user:slug}', function (User $user) {
return $user;
});

Route::get('/posts/{post:slug}', function (Post $post) {
return $post;
});

显式绑定

1
2
3
4
5
6
7
8
9
10
// RouteServiceProvider 或 bootstrap/app.php
use App\Models\User;
use App\Models\Post;

Route::model('user', User::class);
Route::model('post', Post::class);

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

自定义默认行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use App\Models\User;
use Illuminate\Routing\Route;

Route::bind('user', function (string $value, Route $route) {
$withTrashed = $route->parameter('withTrashed', false);

$query = User::query();

if ($withTrashed) {
$query->withTrashed();
}

return $query->where('slug', $value)->firstOrFail();
});

软删除模型绑定

1
2
3
4
5
use App\Models\User;

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

路由组

中间件组

1
2
3
4
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/profile', [ProfileController::class, 'show']);
});

命名空间组

1
2
3
4
Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
});

子域名路由

1
2
3
4
5
6
7
8
9
Route::domain('{account}.example.com')->group(function () {
Route::get('/', function (string $account) {
return "Account: {$account}";
});

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

前缀和名称组

1
2
3
4
Route::prefix('api/v1')->name('api.v1.')->group(function () {
Route::apiResource('users', UserController::class);
Route::apiResource('posts', PostController::class);
});

路由参数约束

正则约束

1
2
3
4
5
6
7
8
9
10
11
Route::get('/users/{id}', function (string $id) {
return User::findOrFail($id);
})->where('id', '[0-9]+');

Route::get('/users/{name}', function (string $name) {
return User::where('name', $name)->firstOrFail();
})->where('name', '[A-Za-z]+');

Route::get('/posts/{slug}', function (string $slug) {
return Post::where('slug', $slug)->firstOrFail();
})->where('slug', '[a-z0-9-]+');

全局约束

1
2
3
4
5
6
7
8
// RouteServiceProvider
public function boot(): void
{
Route::pattern('id', '[0-9]+');
Route::pattern('slug', '[a-z0-9-]+');

parent::boot();
}

编码约束

1
2
3
Route::get('/search/{query}', function (string $query) {
return Search::query($query)->get();
})->where('query', '.*')->utf8();

命名路由

生成 URL

1
2
3
4
5
6
7
8
Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
return compact('user', 'post');
})->name('users.posts.show');

// 生成 URL
$url = route('users.posts.show', [$user, $post]);
$url = route('users.posts.show', ['user' => $user, 'post' => $post]);
$url = route('users.posts.show', [$user->id, $post->slug]);

检查当前路由

1
2
3
4
5
6
7
if (request()->routeIs('users.*')) {
// 当前路由以 users. 开头
}

if (request()->routeIs('users.index', 'users.show')) {
// 当前路由是 users.index 或 users.show
}

资源路由

标准资源路由

1
2
3
4
5
6
7
8
9
10
Route::resource('users', UserController::class);

// 等价于
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
Route::post('/users', [UserController::class, 'store'])->name('users.store');
Route::get('/users/{user}', [UserController::class, 'show'])->name('users.show');
Route::get('/users/{user}/edit', [UserController::class, 'edit'])->name('users.edit');
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');

API 资源路由

1
2
3
4
5
6
7
8
Route::apiResource('users', UserController::class);

// 等价于(排除 create 和 edit)
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::post('/users', [UserController::class, 'store'])->name('users.store');
Route::get('/users/{user}', [UserController::class, 'show'])->name('users.show');
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');

部分资源路由

1
2
Route::resource('users', UserController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['create', 'edit']);

嵌套资源路由

1
2
3
4
5
Route::resource('users.posts', UserPostController::class);

// 生成的路由
// /users/{user}/posts - index
// /users/{user}/posts/{post} - show

浅层嵌套

1
2
3
4
5
Route::resource('users.posts', UserPostController::class)->shallow();

// 生成的路由
// /users/{user}/posts - index
// /posts/{post} - show

资源参数名称

1
2
Route::resource('users', UserController::class)->parameter('users', 'user');
Route::resource('posts', PostController::class)->parameter('posts', 'post:slug');

单动作控制器

1
2
3
4
5
6
7
8
9
10
11
Route::get('/home', HomeController::class);
Route::get('/about', AboutController::class);

// 控制器
class HomeController extends Controller
{
public function __invoke()
{
return view('home');
}
}

回退路由

1
2
3
4
5
6
7
8
Route::fallback(function () {
return response()->view('errors.404', [], 404);
});

// 带约束的回退
Route::fallback(function () {
return redirect()->route('home');
})->where('path', '^(?!api).*$');

路由中间件快捷方式

1
2
3
4
5
6
7
8
9
10
11
12
13
Route::get('/profile', [ProfileController::class, 'show'])
->middleware('auth');

Route::get('/admin', [AdminController::class, 'index'])
->middleware(['auth', 'admin']);

Route::get('/posts/{post}', [PostController::class, 'show'])
->can('view', 'post');

Route::get('/settings', [SettingsController::class, 'index'])
->name('settings')
->middleware('auth')
->withoutMiddleware(VerifyCsrfToken::class);

路由签名 URL

临时签名 URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Illuminate\Support\Facades\URL;

$url = URL::temporarySignedRoute(
'unsubscribe',
now()->addMinutes(30),
['user' => $user->id]
);

Route::get('/unsubscribe/{user}', function (Request $request) {
if (! $request->hasValidSignature()) {
abort(401);
}

// 处理取消订阅
})->name('unsubscribe');

永久签名 URL

1
$url = URL::signedRoute('verify-email', ['user' => $user->id]);

验证签名

1
2
3
4
5
6
7
8
9
Route::get('/verify-email/{user}', function (Request $request, User $user) {
if (! $request->hasValidSignature()) {
return redirect()->route('login')->with('error', 'Invalid link');
}

$user->markEmailAsVerified();

return redirect()->route('dashboard');
})->name('verify-email')->middleware('signed');

路由缓存

缓存路由

1
php artisan route:cache

清除缓存

1
php artisan route:clear

缓存注意事项

1
2
3
4
5
6
7
// 闭包路由不能被缓存
Route::get('/closure', function () {
return 'Hello';
}); // 不会被缓存

// 控制器路由可以被缓存
Route::get('/controller', [Controller::class, 'method']); // 会被缓存

路由模型绑定高级用法

自定义异常

1
2
3
4
5
6
7
8
9
10
use App\Models\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;

Route::bind('user', function (string $value) {
try {
return User::where('slug', $value)->firstOrFail();
} catch (ModelNotFoundException $e) {
throw new UserNotFoundException($value);
}
});

预加载关联

1
2
3
4
5
6
7
8
9
Route::get('/users/{user}', function (User $user) {
return $user;
})->middleware(function ($request, $next) {
$request->route()->setParameter(
'user',
User::with('posts', 'comments')->findOrFail($request->route('user'))
);
return $next($request);
});

条件路由

环境条件

1
2
3
4
Route::when(app()->environment('local'), function () {
Route::get('/dev', [DevController::class, 'index']);
Route::get('/debug', [DebugController::class, 'index']);
});

配置条件

1
2
3
4
Route::when(config('features.blog'), function () {
Route::resource('posts', PostController::class);
Route::resource('categories', CategoryController::class);
});

路由重定向

简单重定向

1
2
3
Route::redirect('/here', '/there');
Route::redirect('/here', '/there', 301);
Route::permanentRedirect('/old', '/new');

带参数重定向

1
Route::redirect('/users/{id}', '/profile/{id}');

路由视图

简单视图路由

1
2
Route::view('/welcome', 'welcome');
Route::view('/about', 'about', ['title' => 'About Us']);

最佳实践

1. 使用路由组组织路由

1
2
3
4
5
6
7
8
9
// 好的做法
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
Route::resource('users', AdminUserController::class);
Route::resource('posts', AdminPostController::class);
});

// 不好的做法
Route::get('/admin/users', [AdminUserController::class, 'index'])->middleware('auth')->name('admin.users.index');
Route::get('/admin/users/create', [AdminUserController::class, 'create'])->middleware('auth')->name('admin.users.create');

2. 合理使用资源路由

1
2
3
4
5
6
7
// 好的做法
Route::apiResource('posts', PostController::class);

// 不好的做法
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::get('/posts/{post}', [PostController::class, 'show']);

3. 使用路由模型绑定

1
2
3
4
5
6
7
8
9
// 好的做法
Route::get('/users/{user}', function (User $user) {
return $user;
});

// 不好的做法
Route::get('/users/{id}', function ($id) {
return User::findOrFail($id);
});

总结

Laravel 13 的路由系统功能强大且灵活,通过合理使用路由模型绑定、路由组、资源路由和签名 URL 等特性,可以构建出清晰、安全、可维护的 URL 结构。记住使用路由缓存来优化生产环境性能,并遵循 RESTful 设计原则来组织 API 路由。