在使用 Casbin 做权限控制时,Model 决定了请求、策略、角色继承和匹配规则怎样组织。很多人刚上手时只会照着示例加载一个 model.conf,但一旦配置来自数据库、需要运行时拼装,或者要把初始化和加载拆开,常见写法就不止一种了。下面按实际开发里的几个问题来整理:先看 Model 有哪些加载入口,再看空 Model 的后续操作方式,方便你根据配置来源和维护成本选择合适方案。
Model 怎么初始化
Casbin 的 Model 常见有三种加载方式:

- 从文件读取
- 从字符串加载
- 动态添加定义
这三种方式本质上都是在构建同一份 Model,只是配置来源不同。项目里最常见的是文件方式;如果配置保存在数据库,字符串方式更直接;如果需要在代码里按条件拼装规则,则更适合动态添加。
从文件加载:最常见的接入方式
如果 Model 已经写在配置文件里,初始化时直接传入文件路径即可。这种方式结构最清晰,也最适合固定权限模型的项目。
// 初始化 model
$m = Enforcer::newModel("path/to/model.conf", "");
// 创建一个 enforcer。
$e = new Enforcer($m);
也可以进一步简化,在初始化 Casbin 决策器时直接传入 Model 文件路径:
$e = new Enforcer("path/to/model.conf");
如果你的权限规则长期稳定、需要和代码分开维护,优先考虑这种方式。它的好处是配置文件独立、可读性高,团队成员也更容易定位请求定义、策略定义和匹配表达式分别写在哪里。
从字符串加载:适合数据库中的配置
有些系统会把 Model 配置存进数据库,或者在后台管理页面里维护。这时不需要先落盘为文件,直接读取文本并交给 Casbin 加载即可。
$text = <<<EOT
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
EOT;
// 使用字符串初始化 model
$m = Enforcer::newModel($text);
// 创建一个 enforcer。
$e = new Enforcer($m);
这类写法适合配置来源灵活的场景,尤其是多租户系统、在线配置中心,或者需要按环境切换权限模型的项目。这里的核心不是“字符串”本身,而是你可以把 Model 作为数据来管理,而不必绑定到某个固定文件。
什么时候用动态加载
如果你不想提前准备完整的配置文件,也不想维护一大段 Model 文本,可以先创建空 Model,再逐项补充定义。这样做更适合需要按程序逻辑拼装配置的场景。
// 初始化 model
$m = Enforcer::newModel();
$m->addDef("r", "r", "sub, obj, act");
$m->addDef("p", "p", "sub, obj, act");
$m->addDef("g", "g", "_, _");
$m->addDef("e", "e", "some(where (p.eft == allow))");
$m->addDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act");
// 创建一个 enforcer。
$e = new Enforcer($m);
这里的 addDef 是关键:它允许你分别添加请求定义、策略定义、角色定义、策略效果和匹配器。相比直接加载整段文本,动态方式更适合在代码中组合不同规则,或者在某些字段需要按条件生成时使用。
不过从维护角度看,动态添加的可读性通常不如配置文件。规则一旦变复杂,排查问题时就要回到代码里逐项确认,因此它更适合“确实需要动态构建”的情况,而不是日常默认选择。
空 Model 还能怎么用
除了在初始化阶段一次性写完定义,Casbin 也支持先创建一个空 Model,再决定后续如何加载内容:

$m = Enforcer::newModel();
有了空 Model 之后,可以根据实际来源选择不同加载方法:
// 从文件加载
$m->loadModel($path);
// 从字符串加载
$m->loadModelFromText($text);
// 动态加载
$m->addDef("r", "r", "sub, obj, act");
这说明 Model 的“创建”和“填充”可以拆成两步。这样做的价值在于,代码结构会更灵活:例如先完成对象初始化,再由不同模块决定从文件、数据库还是运行时参数中补齐定义。
如果你的应用有统一的启动流程,或者要把权限模块封装进框架组件,这种拆分方式会比“初始化时一次传完所有内容”更方便管理。
三种方式该怎么选
实际项目中可以直接按配置来源来判断:
- 配置固定、希望清晰易维护:用文件加载。
- 配置保存在数据库或远程配置中心:用字符串加载。
- 规则需要在代码里按条件拼装:用动态加载。
如果只是普通权限系统,文件方式往往已经足够;只有当 Model 本身变成“可变配置”时,字符串或动态方式的价值才会明显提升。换句话说,三种写法不是能力高低之分,而是适配不同的配置管理方式。









