插件
一个插件是一个目录 plugins/<slug>/,里面必须有 plugin.json。装插件不用改代码,把它放进 plugins/ 再写进 config/plugins.php 就行。
{
"name": "Pages",
"version": "0.1.0",
"description": "站内文档页面",
"license": "GPL-3.0-only",
"requires": { "Nomen": ">=0.1" },
"autoload": { "Nomen\\Plugin\\Pages\\": "src/" },
"boot": ["Nomen\\Plugin\\Pages\\Plugin", "boot"]
}
| 字段 | 说明 |
|---|---|
name / version / description / license | 展示和排查用,Special:Version 会列出来 |
requires.Nomen | 版本约束,比如 ">=0.1";不满足就跳过并在 error_log 里记一条 |
autoload | PSR-4 前缀 → 目录,相对插件根 |
boot | 加载时调一次的回调,用来注册服务、路由 |
hooks | 事件名 → 回调,可以是 ["Class","method"] 或一组这样的数组 |
specialPages | 页面 slug → 定义;access 是 public 或 admin |
站长配置
插件需要的配置放 config/plugins/<slug>.json(也支持 .ini),不要写进插件目录。这样升级插件不会覆盖站长的设置。
{
"base_path": "/w"
}
插件代码用 Plugins::config() 读,第二个参数是默认值:
$config = \Nomen\Src\Plugins::config('pages', ['base_path' => '/w']);
JSON 和 INI 会合并到默认值上;文件不存在就用默认值。格式错了会在 error_log 里记一条。
路由
插件在 boot 里注册路由,路径通常来自配置:
public static function boot(): void
{
$base = self::basePath();
Plugins::route('GET', $base, [Controller::class, 'index']);
Plugins::route('GET', $base . '/{slug}', [Controller::class, 'show']);
Plugins::route('POST', $base . '/{slug}/edit', [Controller::class, 'update'], ['auth', 'admin']);
}
路径支持
{name}占位,处理函数会收到对应的参数。中间件写第三个参数:
auth要求登录,admin要求管理员。插件路由在核心路由之前灌进去,同名的精确路径会被核心覆盖,插件顶不掉
/login、/oauth/token这些入口。
钩子
Hooks::add('user.login.succeeded', [Hooks::class, 'onLogin'], 10); // 数字越小越先跑
// 事件:按顺序调用,回调返回 false 可以中断整条链
Hooks::run('user.login.succeeded', [$userId, $username]);
// 取值:回调拿到当前值,返回非 null 就替换
$claims = Hooks::filter('userinfo.claims', $claims, $user, $scopes);
现在核心会发的钩子:
| 事件 | 类型 | 参数 |
|---|---|---|
user.login.succeeded | run | 用户 id、用户名 |
userinfo.claims | filter | claims 数组、用户行、scopes |
登录、授权这类关键路径上,中断会直接抛异常(run(..., abortable: false) 可以禁止中断),别把安全判断做成钩子回调的结果。
特殊页面
/special/<slug>,注册表在 src/SpecialPages.php。处理函数返回 HTML 字符串,外壳由 templates/special.php 套。核心自带 /special/version 和 /special/plugins。
注意
插件跑的是全权限 PHP,只装信得过的。
插件不要
ALTER核心的表——要加字段就带自己的迁移到database/migrations/,或者用插件自己的表。插件依赖用 composer 装到项目根(
composer require ...),别塞一份vendor/进插件目录。