概述
在 Laravel 13 中,可以通过 PHP 属性来声明控制器的中间件分配和授权检查。无需再使用传统的middleware() 方法或 can 中间件,只需直接在类或方法上添加属性即可完成配置。
use Illuminate\Routing\Attributes\Controllers\Authorize;
use Illuminate\Routing\Attributes\Controllers\Middleware;
#[Middleware('auth')]
class PostController
{
#[Middleware('subscribed')]
#[Authorize('create', Post::class)]
public function store(Request $request): Response
{
// ...
}
}
所有控制器属性都位于
Illuminate\Routing\Attributes\Controllers 命名空间下。#[Middleware] — 分配中间件
应用于类
在类级别添加#[Middleware] 后,该中间件将应用于该控制器的所有操作。
use Illuminate\Routing\Attributes\Controllers\Middleware;
#[Middleware('auth')]
class UserController
{
public function index(): View { /* ... */ }
public function show(User $user): View { /* ... */ }
public function store(Request $request): Response { /* ... */ }
}
#[Middleware('auth')]
#[Middleware('verified')]
class ProfileController
{
// ...
}
应用于方法
在方法级别添加的中间件会与类级别的中间件合并。#[Middleware('auth')]
class UserController
{
// 所有人只应用 auth
public function index(): View { /* ... */ }
// 同时应用 auth + subscribed
#[Middleware('subscribed')]
public function create(): View { /* ... */ }
}
使用 only / except 进行筛选
在类级别的属性上指定 only 或 except 可以对目标方法进行筛选。
#[Middleware('auth')]
#[Middleware('subscribed', only: ['create', 'store', 'edit', 'update'])]
class ArticleController
{
// 仅 auth
public function index(): View { /* ... */ }
public function show(Article $article): View { /* ... */ }
// auth + subscribed
public function create(): View { /* ... */ }
public function store(Request $request): Response { /* ... */ }
public function edit(Article $article): View { /* ... */ }
public function update(Request $request, Article $article): Response { /* ... */ }
// 仅 auth(相当于被 except 排除的情况)
public function destroy(Article $article): Response { /* ... */ }
}
闭包中间件
属性也支持传入闭包,方便内联编写处理逻辑。use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Attributes\Controllers\Middleware;
class ReportController
{
#[Middleware(static function (Request $request, Closure $next) {
if (! $request->user()->hasRole('analyst')) {
abort(403);
}
return $next($request);
})]
public function generate(): Response
{
// ...
}
}
与传统 middleware() 方法的对比
// 传统写法(实现 HasMiddleware 接口)
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class UserController implements HasMiddleware
{
public static function middleware(): array
{
return [
'auth',
new Middleware('log', only: ['index']),
new Middleware('subscribed', except: ['store']),
];
}
}
// 使用属性的写法
#[Middleware('auth')]
#[Middleware('log', only: ['index'])]
#[Middleware('subscribed', except: ['store'])]
class UserController
{
// 无需实现 HasMiddleware
}
使用属性时无需实现
HasMiddleware 接口。但如果将 middleware() 方法与属性混合使用,可能会产生非预期的行为,因此建议只使用其中一种方式。#[WithoutMiddleware] — 排除中间件
从特定方法或整个类中排除在类级别应用的中间件。
应用于方法
use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Routing\Attributes\Controllers\Middleware;
use Illuminate\Routing\Attributes\Controllers\WithoutMiddleware;
#[Middleware('auth')]
#[Middleware(EnsureTokenIsValid::class)]
class ApiController
{
// 同时应用 auth 和 EnsureTokenIsValid
public function show(Resource $resource): JsonResponse { /* ... */ }
// 应用 auth,但排除 EnsureTokenIsValid
#[WithoutMiddleware(EnsureTokenIsValid::class)]
public function index(): JsonResponse { /* ... */ }
}
应用于类与 only / except
在类级别添加 #[WithoutMiddleware] 后,中间件会从包含子类在内的所有操作中被排除。可以通过 only / except 限定排除范围。
#[Middleware('auth')]
#[WithoutMiddleware('subscribed', except: ['index'])]
class AdminController
{
// 应用 subscribed(未被 except: ['index'] 排除)
public function index(): View { /* ... */ }
// 排除 subscribed
public function dashboard(): View { /* ... */ }
public function settings(): View { /* ... */ }
}
#[WithoutMiddleware] 仅对路由中间件有效。无法排除注册在 app/Http/Kernel.php 中的全局中间件。#[Authorize] — 基于策略的授权
作为 can 中间件的语法糖,可以通过属性以声明式方式进行基于策略的授权检查。
基本用法
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;
class PostController
{
// 检查 'viewAny' 能力(Post 策略的 viewAny 方法)
#[Authorize('viewAny', Post::class)]
public function index(): View { /* ... */ }
// 检查 'view' 能力(传入路由参数 'post')
#[Authorize('view', 'post')]
public function show(Post $post): View { /* ... */ }
// 检查 'create' 能力
#[Authorize('create', Post::class)]
public function create(): View { /* ... */ }
#[Authorize('create', Post::class)]
public function store(Request $request): Response { /* ... */ }
// 检查 'update' 能力(传入路由参数)
#[Authorize('update', 'post')]
public function edit(Post $post): View { /* ... */ }
#[Authorize('update', 'post')]
public function update(Request $request, Post $post): Response { /* ... */ }
#[Authorize('delete', 'post')]
public function destroy(Post $post): Response { /* ... */ }
}
策略的参数
第二个参数可以传入以下值:| 传入的值 | 说明 |
|---|---|
Post::class | 模型类(用于 viewAny 等不需要模型实例的情况) |
'post' | 路由参数名(会传入经过模型绑定的实例) |
[Comment::class, 'post'] | 多个参数(以数组形式传给策略方法) |
use App\Models\Comment;
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;
class CommentController
{
// 将 $post(路由参数)和 Comment 类传给策略
#[Authorize('create', [Comment::class, 'post'])]
public function store(Post $post, Request $request): Response
{
// ...
}
#[Authorize('delete', 'comment')]
public function destroy(Comment $comment): Response
{
// ...
}
}
与 can 中间件的对比
// 传统写法(Route 门面)
Route::get('/posts', [PostController::class, 'index'])->middleware('can:viewAny,App\Models\Post');
Route::put('/posts/{post}', [PostController::class, 'update'])->middleware('can:update,post');
// 使用属性的写法
class PostController
{
#[Authorize('viewAny', Post::class)]
public function index(): View { /* ... */ }
#[Authorize('update', 'post')]
public function update(Request $request, Post $post): Response { /* ... */ }
}
实践示例
应用于资源控制器
use App\Models\Article;
use Illuminate\Routing\Attributes\Controllers\Authorize;
use Illuminate\Routing\Attributes\Controllers\Middleware;
#[Middleware('auth')]
class ArticleController
{
#[Authorize('viewAny', Article::class)]
public function index(): View
{
return view('articles.index', [
'articles' => Article::paginate(),
]);
}
#[Authorize('view', 'article')]
public function show(Article $article): View
{
return view('articles.show', compact('article'));
}
#[Authorize('create', Article::class)]
public function create(): View
{
return view('articles.create');
}
#[Authorize('create', Article::class)]
public function store(StoreArticleRequest $request): RedirectResponse
{
$article = Article::create($request->validated());
return redirect()->route('articles.show', $article);
}
#[Authorize('update', 'article')]
public function edit(Article $article): View
{
return view('articles.edit', compact('article'));
}
#[Authorize('update', 'article')]
public function update(UpdateArticleRequest $request, Article $article): RedirectResponse
{
$article->update($request->validated());
return redirect()->route('articles.show', $article);
}
#[Authorize('delete', 'article')]
public function destroy(Article $article): RedirectResponse
{
$article->delete();
return redirect()->route('articles.index');
}
}
应用于 API 控制器
use Illuminate\Routing\Attributes\Controllers\Middleware;
use Illuminate\Routing\Attributes\Controllers\WithoutMiddleware;
#[Middleware('auth:sanctum')]
#[Middleware('throttle:api')]
class ApiPostController
{
// 列表获取无需认证
#[WithoutMiddleware('auth:sanctum')]
public function index(): JsonResponse
{
return response()->json(Post::paginate());
}
public function store(Request $request): JsonResponse
{
// 仅限已认证用户
}
}
属性的处理顺序
当添加了多个属性时,处理顺序如下:#[Authorize] 在内部作为 can 中间件运行,因此与 #[Middleware] 在同一管道中被处理。总结:应该使用哪种方式
| 场景 | 推荐方案 |
|---|---|
| 新建控制器(Laravel 13) | 使用属性 |
已有 middleware() 方法的控制器 | 逐步迁移到属性 |
| 需要动态的中间件配置 | 继续使用 middleware() 方法 |
| 希望在路由文件中统一管理 | 继续使用 Route::middleware() |
后续步骤
PHP 属性(队列 / Eloquent)
介绍可用于队列任务和 Eloquent 模型的 PHP 属性。
进阶:控制器
学习 Laravel 控制器的基础用法。