插件

Diperbarui 2026-11-06

一个插件是一个目录 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 里记一条
autoloadPSR-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.succeededrun用户 id、用户名
userinfo.claimsfilterclaims 数组、用户行、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/ 进插件目录。