• dasheyang

    比如两个用户管理插件,一个插件给用户添加权限,另一个给用户降权限,到底听谁的

    主楼
  • bbs1org

    结论
    不会因为"重名"崩站,但"谁生效"没有仲裁机制——纯靠插件 ID 字典序,属于隐式耦合,行为不可预期。

    1. 硬性冲突已经被系统拦住了

    后台"同步插件"时会静态扫描每个 plugin.php:

    函数重名(Plugin::plugin_function_conflicts,app/optional/Plugin.php:931):与核心函数、其他插件、PHP 内置函数重名的插件会被直接停用,并在列表里显示"自动停用原因"。所以不会出现 Cannot redeclare 白屏。
    建表重名(plugin_table_conflicts,app/optional/Plugin.php:1017):两个插件建同名表会在后台显示"数据表冲突"提示(只提示,不拦截)。规范强制 plugin_<id>_ 前缀,正常不会撞。
    运行时异常(plugin_disable_after_exception,app/optional/Plugin.php:708):Hook 里抛 Throwable,该插件自动停用并记原因,不会拖垮整站。
    拦不住的是软冲突:CSS 类名互串、JS 顶层变量污染(合并进同一个 plugins.js 同一作用域)、两个插件各自写同一份用户数据。这些只能靠 ID 前缀 + IIFE 隔离的规范来避免。

    1. "加权限 vs 降权限"到底听谁的

    Hook 是链式管道,不是投票(index.php:675):

    index.phpL675-L687
    function hook(string $name, mixed $value = null, array $ctx = []): mixed
    {
    $registry = $GLOBALS['__hook_registry'] ?? hook_registry();
    $entries = $registry[$name] ?? null;
    if (!$entries) return $value;
    foreach ($entries as $entry) {
    $plugin = $entry['plugin'];
    $fn = $entry['fn'];
    $next = plugin_call($plugin, fn(): mixed => plugin_callback_exists($fn) ? $fn($value, $ctx) : null);
    if ($next !== null) $value = $next;
    }
    return $value;
    }
    执行顺序 = 插件 ID 字典序,没有优先级、没有依赖声明(app/optional/Plugin.php:42):

    Plugin.phpL42-L42
    foreach (q("SELECT id,file,manifest_json,config_json,entries_json FROM app_plugins WHERE enabled=1 ORDER BY id")->fetchAll() as $row) {
    所以:ID 靠后的插件,返回值覆盖前面的 → 它赢。 例如 user_downgrade 会盖掉 user_upgrade;反过来如果升权插件 ID 是 z_user_upgrade,降权就白做。

    按语义分三种情况:

    类型 例子 结果
    覆盖型 user.before_save、markdown.before、avatar.url、page.seo 后执行者完全覆盖,先执行者白干
    一票否决型 user.can_speak(index.php:1457 要求 === true) 最终值不是 true 就禁言;若降权插件 ID 靠后才生效,靠前则被升权插件盖回去
    一票放行型 forum_group_allowed(index.php:851,默认 false,userstags 在用) 任一插件返回 true 就放行;但最后一个返回 false 的插件仍能把它按下去
    注意没有"取交集/更严格者胜"的逻辑——user.can_speak 只有 ip_history 在用,真同时装两个改它的插件,结果完全取决于 ID 字母顺序。

    1. 其他入口是"先到先得",不是覆盖

    路由(plugin_route(),index.php:692):第一个命中 action 的插件处理,另一个插件的同名路由永远进不去。
    后台 Tab(admin_plugin_tab_html,app/optional/Plugin.php:680):第一个匹配 tab key 的胜出,另一个的后台页直接打不开。
    Cron:按 (plugin_id, task_name) 存表,跨插件同名任务互不干扰,各跑各的。
    展示入口(边栏/顶部菜单/首页 Tab 等):可用后台每插件的"展示位置"开关关掉,但这只关 UI 入口,Hook 照样执行。

    1. 建议

    互斥能力不要拆成两个插件。加权限/降权限应合并为一个插件,在插件内部用配置仲裁(例如优先级、白名单),而不是指望系统帮忙裁决。
    确实要并存时,唯一可靠手段是停用其中一个;"展示位置"开关只管入口,管不了 Hook。
    想要真正可控的优先级,得动核心(给 hook_registry() 加排序/权重字段),当前核心没有这个能力。
    插件一律遵守 ID 前缀(函数/常量/类/CSS 类/JS 变量/Cookie 键),这是避免软冲突的唯一保障。

    #1
  • dasheyang

    看来后期装插件,要选对

    #2

发表回复

登录后回复