1.2 插件目录结构与文件命名规范

把文件放对位置,后缀选对,Discuz! 才认你的插件。

1.2.1 插件放在哪:source/plugin/{唯一标识符}/

所有插件代码都集中在 source/plugin/ 下,每个插件一个子目录,目录名就是你在后台填的唯一标识符(identifier)。

source/plugin/
├── myplugin/                 ← 唯一标识符就是 myplugin
│   ├── index.php
│   ├── core.inc.php
├── otherplugin/

标识符一旦定了就不能改,因为后台菜单、模块定义、钩子类名、XML 清单里的 identifier 字段全指着它。命名规则跟 PHP 变量一致:字母或下划线开头,只含字母、数字、下划线,全站唯一。

X5.0 提供了 DISCUZ_PLUGIN() 常量代替硬编码路径:

require_once DISCUZ_PLUGIN('myplugin').'/core.inc.php';   // 等价于 source/plugin/myplugin/core.inc.php

同系列还有 DISCUZ_TEMPLATE()(代替 /template/xxx 目录)、DISCUZ_DATA(代替 /data 目录)、DISCUZ_ROOT_STATIC(对外展示静态文件的目录)。涉及前端调用的 URI 不用改,后台目录里写死路径的地方则必须改。

1.2.2 后缀就是约定

Discuz! 不靠配置文件说明哪个文件干什么,它靠后缀。这套约定是硬性的,写错了文件就不会被加载:

后缀 用途 放在哪
.php 可直接通过浏览器访问的普通程序文件 插件根目录
.inc.php 被普通程序文件引用的程序文件 插件根目录
.func.php 函数库 插件根目录
.class.php 类库 插件根目录
.htm 模板文件 template/ 子目录
.htm(手机版) 手机版模板 template/mobile/ 子目录
.lang.php 语言包 开发时放 data/plugindata/{标识}.lang.php
table_{表名}.php 插件自己的 Table 类 table/ 子目录

.class.php 结尾的脚本类对应「程序脚本」模块,.inc.php 结尾的对应「扩展项目」。后缀直接决定后台能不能识别成模块。

1.2.3 我想实现 X,该放哪个文件

按实际需求倒推,动手前查一遍:

我要做的事 文件放哪 用什么后缀
能被浏览器直接打开的独立页面 插件根目录 .php
被主程序 include 的业务逻辑 插件根目录 .inc.php
一堆可复用的函数 插件根目录 .func.php
钩子类(plugin_标识_CURSCRIPT) 插件根目录 .class.php
普通版页面模板 template/ .htm
手机版页面模板 template/mobile/ .htm
界面上的中文文案 开发期放 data/plugindata/ .lang.php
插件自有数据表的 CRUD table/ table_{表名}.php
运行时生成的缓存或锁 站点 data/cache/ 按需

1.2.4 一棵完整的插件目录树

以标识符 myplugin 为例:

source/plugin/myplugin/
├── index                    ← 空 index 文件,防目录浏览
├── core.inc.php             ← 主业务逻辑
├── admin.inc.php            ← 后台管理页脚本
├── myplugin.func.php        ← 函数库
├── myplugin.class.php       ← 钩子类
├── install.php / uninstall.php / upgrade.php
├── template/
│   ├── admin.htm            ← 与 admin.inc.php 同名
│   └── mobile/
│       └── myplugin.htm     ← 手机版模板
├── table/table_myplugin_log.php
├── adv/adv_myadv.php        ← 广告类扩展(可选)
├── magic/magic_mymagic.php  ← 道具类扩展(可选)
└── task/task_mytask.php     ← 任务类扩展(可选)

data/plugindata/myplugin.lang.php   ← 开发时的语言包
data/cache/                          ← 动态缓存
data/xxx.sql                         ← SQL 备份
  • template/ 下的模板必须与脚本同名,这是「扩展项目」自动找到模板的前提。
  • adv/、magic/、task/ 目录名不能改,X3.0 起脚本固定放这三个位置。
  • 空 index 文件不带扩展名,内容为空即可。不认空文件的服务器,放一行 <?php exit;?> 也行。

1.2.5 Table 类:插件自己的数据层

table/ 下的 table_{表名}.php 是插件自定义表的模型层,调用方式和系统内置的 Table 类一致。假设插件有张日志表 myplugin_log:

<?php
// source/plugin/myplugin/table/table_myplugin_log.php
class table_myplugin_log extends DB_TABLE {
    public static function add_log($data) {
        return DB::insert('myplugin_log', $data);
    }
}

调用方式:

C::t('myplugin_log')->add_log(['uid' => $_G['uid'], 'time' => TIMESTAMP]);
DB::table('myplugin_log');   // 带前缀的完整表名,例如 pre_myplugin_log

DB::table() 会自动加上安装时设定的前缀(官方文档写作 cdb_,X5.0 数据字典写作 pre_)。不要硬写表前缀,用户改过前缀的站点会直接崩。

1.2.6 关于语言包的位置

开发阶段,语言包写在 data/plugindata/{标识}.lang.php,打包发布时并入 XML 清单,安装时由系统释放。

广告类、道具类、任务类的语言包不走这个机制,各有固定位置:

扩展类 语言包位置 附件
广告类 source/language/adv/lang_{name}.php 同名 .gif
道具类 source/language/magic/lang_{name}.php .small.gif / .gif
任务类 source/language/task/lang_{name}.php .gif

X5.0 的模板语言包还支持 i18n 扩展,路径形如 /template/i18n/SC_UTF8/lang_template.php。

1.2.7 命名规范的实际影响

两个约定值得强调,因为违反后 debug 很痛苦:

第一,模板与脚本同名。后台定义「扩展项目」模块时脚本填 admin.inc.php,系统就去找 template/admin.htm。名字对不上,页面白屏或模板为空,新手最常撞这堵墙。

第二,Table 类文件名与表名严格对应。表 myplugin_log 对应文件必须是 table_myplugin_log.php,类名必须是 table_myplugin_log。

运行时缓存写在站点 data/cache/ 下,用 DISCUZ_DATA.'cache/xxx.php' 拼路径。别往插件目录写运行时文件——该目录可能不可写,也可能随打包被覆盖。

1.2.8 本节要点

  • 插件根目录 source/plugin/{唯一标识符}/,标识符不可改。
  • 后缀决定行为:.php 可直访,.inc.php 被引用,.func.php 函数库,.class.php 钩子类。
  • 模板放 template/,手机版放 template/mobile/,且必须与脚本同名。
  • Table 类放 table/,命名 table_{表名}.php,表前缀一律走 DB::table()。
  • 目录里放个空 index 文件防目录浏览;运行时缓存写站点 data/cache/。

下一篇讲运行环境:class_core 都干了什么,$_G 里有哪些能直接用的值。