[开发]SoSite 插件系统介绍

👑Lv.11 元老 🌞 骨干会员
2026-10-25 00:53:13

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:8月13日备份/sosite-plugin-dev/plugin-system.md。

SoSite 插件系统介绍

适用版本:SoSite V5.0.0(PHP 8.2 / MySQL 5.7-8.0,自研 MVC,零 Composer 依赖)

统计日期:2026-08-09(与当前源码逐行核对)

关联文档:本目录 SKILL.md(开发规范主入口)、references/01-plugin-structure.md(结构规范)、references/02-hooks-catalog.md(hooks 速查)


一、插件系统架构

1.1 核心设计

项说明
插件目录plugins/{dir}/({dir} 小写下划线,如 daily_checkin)
清单文件plugins/{dir}/plugin.json(名称/版本/作者/描述/激活态/hooks 映射)
缓存文件plugins/plugins_cache.php(var_export 生成,json_decode 读取;临时文件 + rename 原子写入)
命名空间Plugin\{Dir}(Autoloader 自动映射,类如 Plugin\DailyCheckin\CheckinController)
视图解析resolveTemplate 插件目录优先:view('plugins/xxx/yyy') → plugins/xxx/views/yyy.php(回落 app/Views)
资源引用直接 /plugins/{dir}/assets/...(勿用 asset(),避免 assets/plugins/ 双份漂移)

1.2 加载流程(每次请求)

index.php / admin 入口
  → app/init.php: $GLOBALS['__view'] = new TemplateCompiler()
  → \app\Helpers\Plugin::init()          # 读缓存(无则扫描目录建缓存)
  → Plugin::hook('init_after')           # 初始化钩子
  → 路由注册: Plugin::hook('route_register' | 'admin_route_register')
  → dispatch: Plugin::hook('route_before_dispatch') → 控制器 → Plugin::hook('route_after_dispatch')

1.3 Hook 执行机制(Plugin::hook($name, $params))

foreach (已启用的插件 as $config) {
    if ($config['hooks'][$name] 存在) {
        $hookFile = plugins/{dir}/ + 相对路径;
        // 路径安全:realpath 校验必须在插件目录内(防穿越),违规跳过
        include $hookFile;              // extract($params) 注入变量
        // 异常被 try/catch 捕获,单钩子失败不影响页面
    }
}

  • 仅启用(plugin.json activated: true)的插件参与
  • 参数经 extract(EXTR_SKIP | EXTR_REFS) 注入(&$data 引用型参数可回写)
  • 钩子文件内可访问调用点作用域变量(如 $router、$view)
  • 单个钩子抛异常只 error_log,不影响页面继续

二、插件生命周期

阶段触发方式插件可做的事
加载Plugin::init()(每请求)—
启用后台"插件管理" → Plugin::activate()Plugin.php::activate() 建表(幂等 CREATE TABLE IF NOT EXISTS);返回 false 回滚启用
禁用后台 → Plugin::deactivate()Plugin::deactivate() 可清理运行时状态
卸载后台 → Plugin::uninstall()Plugin::uninstall() 自行 DROP 表/清理数据(框架只删注册与缓存)
缓存刷新改 plugin.json 后Plugin::refresh() 重建 plugins_cache.php

插件类约定(可选):

namespace Plugin\Xxx;
class Plugin {
    public static function activate(): bool   { return true; }
    public static function deactivate(): bool { return true; }
    public static function uninstall(): void  {}
}


三、核心 Hook 总表(21 个,源码逐行核对)

触发文件 = 调用 Plugin::hook('name', ...) 的位置。

3.1 生命周期与全局

Hook触发文件参数用途
init_afterapp/init.php—请求初始化后(懒建表/一次性初始化)
controller_view_beforeapp/Core/Controller.phptemplate、data(引用,可改)视图渲染前注入/修改数据
route_before_dispatchindex.phprouter路由执行前拦截/全局处理
route_after_dispatchindex.php—请求收尾(计数落库等)

3.2 路由与导航

Hook触发文件参数用途
route_registerindex.phprouter注册前台路由
admin_route_registerindex.php(后台分组)router注册后台路由(URL 不带 /admin 前缀)
admin_sidebar_linksapp/Views/admin/_sidebar.php$view后台侧边栏入口
nav_main_linksapp/Views/layouts/main.php$view前台主导航
nav_plugin_linksmain.php + _components/left_sidebar.php(两处)$view(可 null)"应用"下拉/左栏入口
nav_user_menu_itemsmain.php 用户下拉菜单$view用户菜单入口(2026-08-09 补触发点,历史曾无调用点)

3.3 页面注入

Hook触发文件参数用途
layout_head_endapp/Views/layouts/main.php$view、$bp</head> 前注入 CSS/JS/meta
home_sidebar_bottomright_sidebar.php + mobile_sidebar.php(两处)—侧边栏底部内容
announcements_displayhome/index.php + forum/index.php(两处)—公告条输出

3.4 用户行为

Hook触发文件参数用途
auth_login_afterapp/Controllers/AuthController.phpuser_id、username登录后联动
auth_register_beforeAuthController.php—注册前校验;错误传 $GLOBALS['auth_register_error']
auth_register_afterAuthController.phpuser_id、username、email注册成功后联动
avatar_upload_afterapp/Controllers/ProfileController.phpuser_id、avatar、old_avatar头像上传后(审核等)

3.5 内容行为

Hook触发文件参数用途
thread_create_afterapp/Controllers/PostController.phpthread_id、user_id、category_id发帖后
post_create_afterapp/Controllers/ThreadController.phppost_id、thread_id、user_id回帖后
thread_edit_afterThreadController.phpthread_id、user_id编辑主题后
post_edit_afterPostController.phppost_id、thread_id、user_id编辑回复后


四、插件 Hook 注册分布(14 个插件)

[启用] = plugin.json activated: true;[停用] = 注册但未启用(钩子不执行)。

插件版本状态注册的 hooks
announcements 站点公告1.2.0[启用]init_after, announcements_display, admin_route_register, admin_sidebar_links
content_review 内容审核2.0.0[停用]init_after, route_register, admin_route_register, admin_sidebar_links, thread_create_after, post_create_after, auth_register_after, avatar_upload_after, controller_view_before, nav_user_menu_items, nav_main_links, layout_head_end
daily_checkin 每日签到1.0.0[启用]init_after, route_register, controller_view_before, nav_plugin_links, nav_user_menu_items
dice 摇骰子1.0.0[启用]init_after, route_register, nav_main_links, nav_plugin_links
edit_info 编辑信息1.5.0[启用]init_after, post_edit_after, thread_edit_after, controller_view_before
friend_links 友情链接2.0.0[启用]init_after, route_register, admin_route_register, admin_sidebar_links, home_sidebar_bottom
icon_generator 图标生成器1.0.0[启用]route_register, nav_plugin_links
invite 邀请注册1.5.0[停用]init_after, route_register, auth_register_before, auth_register_after, nav_plugin_links, layout_head_end
post_favorite 帖子收藏1.0.0[启用]init_after, route_register, nav_user_menu_items, layout_head_end
qa 问答系统3.0.0[停用]init_after, route_register, admin_route_register, admin_sidebar_links, nav_main_links, nav_user_menu_items
quiz_duel 猜题对决1.0.0[启用]init_after, route_register, nav_plugin_links
red_packet 积分红包1.8.0[启用]init_after, route_register, nav_plugin_links
seo SEO 优化1.2.0[启用]init_after, route_register, admin_route_register, admin_sidebar_links, layout_head_end
user_profile 用户主页1.0.0[启用]init_after, route_register, admin_route_register, admin_sidebar_links, nav_plugin_links

汇总:14 个插件、11 启用 / 3 停用(content_review、invite、qa);插件共注册 18 个不同 hook 名。


五、Hook 使用统计(注册次数)

Hook注册插件数说明
init_after13几乎每个插件都用(建表/初始化)
route_register12前台路由注册标配
nav_plugin_links7应用入口
admin_route_register / admin_sidebar_links各 6后台管理类
nav_user_menu_items / layout_head_end各 4用户菜单 / head 注入
controller_view_before / nav_main_links各 3视图注入 / 导航
auth_register_after2注册联动
其余(announcements_display、auth_login_after、auth_register_before、avatar_upload_after、home_sidebar_bottom、post_create_after、post_edit_after、thread_create_after、thread_edit_after)各 1专用钩子


六、插件系统 API(app\Helpers\Plugin)

Plugin::init();                      // 加载插件(缓存优先)
Plugin::hook($name, $params);        // 触发钩子
Plugin::getPlugins();                // 全部插件配置数组
Plugin::getPlugin($name);            // 单个插件配置
Plugin::isActivated($name);          // 是否启用
Plugin::activate($name);             // 启用(写 plugin.json + 调 Plugin.php::activate + 缓存)
Plugin::deactivate($name);           // 禁用
Plugin::updateConfig($name, $data);  // 更新白名单字段(name/version/author/description/activated/hooks)
Plugin::refresh();                   // 重建缓存(改 plugin.json 后必调)
Plugin::uninstall($name);            // 卸载(调 Plugin.php::uninstall + 移除注册)

后台入口:/admin/plugins(列表/启用/禁用/编辑配置/卸载),由 app/Controllers/Admin/PluginController 驱动。


七、已知坑与注意点(本系统特有)

  1. nav_user_menu_items 历史无触发点:插件注册了该钩子但核心长期无 Plugin::hook('nav_user_menu_items') 调用(用户菜单入口不显示);2026-08-09 已在 app/Views/layouts/main.php 移动端用户下拉菜单补触发点。若仍不生效,检查核心布局是否有该调用。
  2. 同一 hook 多处触发(nav_plugin_links 两处、home_sidebar_bottom 两处、announcements_display 两处):hook 文件会被 include 多次,输出会重复——用静态变量守卫或把只输出一次的逻辑放 init_after。
  3. auth_register_before 传错约定:必须写 $GLOBALS['auth_register_error'],框架 hook 后读取非空即拦截注册。
  4. 钩子内禁止 exit/die:会终止整页;抛异常会被捕获。
  5. 视图对象可能为 null:hook 内用 $view = $GLOBALS['__view'] ?? null; 并兜底(icon 不可用时用 emoji/纯文本)。
  6. PDO 两条铁律(全局 ATTR_EMULATE_PREPARES=false):同一命名占位符不能复用(HY093);SHOW 语句不能有占位符(1064,$conn->quote() 内联)。
  7. 插件资源引用:直接 /plugins/{dir}/assets/...,勿用 asset()(会解析到 assets/plugins/ 发布目录,双份漂移)。
  8. plugins_cache.php 是运行时产物:上传插件目录时不要覆盖它(含服务器上的启停配置),改 plugin.json 后 Plugin::refresh()。

八、配套文档索引

文档内容
SKILL.md开发主入口:触发条件/硬规则/工作流
references/01-plugin-structure.md目录结构、plugin.json、生命周期、最小示例
references/02-hooks-catalog.mdhooks 速查(本文件的精简版)
references/03-api-cheatsheet.mdAPI 速查(11 大类)
references/04-security.md安全规范与已知坑
references/05-ui-templates.md模板/UI/资源引用规范
references/06-troubleshooting.md排错清单
references/07-plugin-patterns.md典型插件模式与实现模板
plugin-system.md(本文件)插件系统架构、全部 hooks、注册分布、生命周期

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 7 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入