硬编码权限校验导致维护脆弱、测试成本高、易漏判;装饰器模式通过链式调用将“谁可以做什么”与业务逻辑分离,新增规则只需添加类并注册,不修改原有代码。

权限校验逻辑为什么不能硬编码在控制器里
硬编码权限判断(比如 if ($user->role !== 'admin') die('no access'))会让后续加「编辑自己文章」或「VIP用户下载加速」这类新规则变得脆弱。每次改都要动业务代码,测试成本高,还容易漏掉某个入口点。
装饰器模式在这里的价值不是炫技,而是把「谁可以做什么」从「怎么做」里剥离开。你新增一个权限规则,只需要写一个新类,注册到链里,不碰原有逻辑。
用PHP实现权限装饰器链的关键结构
核心是让每个装饰器实现统一接口,并持有下一个处理者($next)。它不决定最终放行与否,只做自己的判断:通过就交给下一个,不通过就直接返回拒绝响应。
- 定义接口
PermissionChecker,含check($user, $resource, $action)方法 - 基础装饰器如
RoleBasedChecker检查角色,OwnershipChecker检查资源归属 - 组合时用构造函数传入
$next,例如:new OwnershipChecker(new RoleBasedChecker(new DefaultDenyChecker())) - 最末端必须是兜底装饰器(如
DefaultDenyChecker),避免漏判返回 null 导致静默放行
示例片段:
class OwnershipChecker implements PermissionChecker
{
private $next;
public function __construct(PermissionChecker $next) {
$this->next = $next;
}
public function check($user, $resource, $action) {
if ($action === 'edit' && $resource->owner_id === $user->id) {
return true;
}
return $this->next->check($user, $resource, $action);
}
}
装饰器顺序为什么直接影响权限语义
顺序不是随意的。比如把 RateLimitChecker 放在鉴权之前,会导致未登录用户也被限流;把 VIPFeatureChecker 放在 RoleBasedChecker 之后,才能确保只有已通过角色检查的用户才进入 VIP 判断。
- 推荐顺序:认证前置(如 token 解析)→ 基础角色/组权限 → 资源级动态权限(归属、状态、时间窗)→ 特殊策略(VIP、灰度、A/B)→ 默认拒绝
- 避免循环依赖:装饰器内部不能反向调用自身或上游装饰器
- 调试时可在每个
check()开头加日志,输出当前装饰器名和判断结果,快速定位卡在哪一环
实际集成到 Laravel 或原生路由时的坑
很多人卡在“怎么让装饰器链接入请求生命周期”。不是所有框架都支持中间件式链式调用,硬塞进控制器会失去装饰器意义。
- Laravel 场景:把装饰器链封装成自定义中间件,在
handle()中调用根装饰器的check(),失败则 throwAuthorizationException - 原生 PHP:在路由分发前统一拦截,用
$_SERVER['REQUEST_URI']和$_SERVER['REQUEST_METHOD']推导$resource和$action,避免每个路由手动传参 - 常见错误:
check()返回null而非布尔值,导致if ($result)判定为 false —— 必须严格返回true或false - 性能注意:不要在装饰器里做 N+1 查询,
$user和$resource应该由上层预加载好,装饰器只做逻辑判断
复杂点在于资源抽象——$resource 不能只是 ID,得是带元数据的对象(比如有 status、created_at、owner_id 的实例),否则所有权或状态类规则没法写。这点容易被忽略,等加第二个动态规则时才返工。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











