如何避免重复编写Laravel模型策略函数?支持控制器与视图权限校验
问题与解决方案
目前项目里每个模型的策略函数都要重复编写类似代码,冗余且低效;同时需要一套控制器与视图通用的权限校验方式,避免两边各写一套逻辑。
现有策略示例:
public function index(User $user) { return $user->hasPermission('view-post'); }
解决步骤
1. 编写通用策略基类,告别重复策略代码
直接创建一个通用基类,用魔术方法自动处理所有模型的权限校验,无需为每个模型单独写策略类:
namespace App\Policies; use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Database\Eloquent\Model; use App\Models\User; class BasePolicy { use HandlesAuthorization; // 自动匹配所有动作的权限校验 public function __call($method, $args) { $user = $args[0]; $model = $args[1] ?? null; // 自动生成权限标识,比如Post模型的index动作对应view-post $modelName = strtolower(class_basename($model)); $permissionName = match($method) { 'index', 'view' => "view-{$modelName}", 'create', 'store' => "create-{$modelName}", 'edit', 'update' => "update-{$modelName}", 'destroy' => "delete-{$modelName}", default => "{$method}-{$modelName}", }; return $user->hasPermission($permissionName); } }
然后在AuthServiceProvider中把所有需要权限控制的模型绑定到这个基类:
namespace App\Providers; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use App\Policies\BasePolicy; use App\Models\Post; use App\Models\Comment; class AuthServiceProvider extends ServiceProvider { protected $policies = [ Post::class => BasePolicy::class, Comment::class => BasePolicy::class, // 其他需要权限控制的模型都绑定到这里 ]; public function boot() { $this->registerPolicies(); } }
2. 优化User模型的hasPermission方法
原方法存在变量错误与查询冗余,优化后通过关联查询更高效:
public function hasPermission($permission) { // 依赖User模型的role关联,需提前定义 return $this->role && $this->role->permissions() ->where('permission', $permission) ->exists(); } // User模型补充role关联 public function role() { return $this->belongsTo(Role::class); } // Role模型补充permissions关联(需提前定义) // public function permissions() // { // return $this->belongsToMany(Permission::class, 'role_permissions'); // }
3. 控制器与视图通用的权限校验方式
控制器端使用
保持原有调用逻辑即可,无需额外修改:
public function index(User $user) { // 方式一:手动判断并跳转 if ($user->cannot('index', Post::class)) { return redirect('/'); } // 方式二:自动抛出403异常 // $this->authorize('index', Post::class); }
视图端使用
直接用Blade原生的@can/@cannot指令,底层会自动调用策略校验逻辑:
@can('index', \App\Models\Post::class) <a href="{{ route('posts.index') }}">查看文章列表</a> @endcan @cannot('create', \App\Models\Post::class) <p>您没有创建文章的权限</p> @endcannot
4. 可选:全局辅助函数增强灵活性
如果需要直接校验权限字符串,可在app/helpers.php中添加全局辅助函数:
function has_permission($permission) { return auth()->user()?->hasPermission($permission) ?? false; }
之后控制器与视图均可直接调用:
// 控制器中 if (!has_permission('view-post')) { return redirect('/'); }
<!-- 视图中 --> @if(has_permission('create-post')) <button type="button">创建文章</button> @endif
内容的提问来源于stack exchange,提问作者mmdev
相关产品推荐
相关产品推荐

