- 企业应用
- 后端
【免费下载链接】dolibarr
Dolibarr ERP CRM is a modern software package to manage your company or foundation's activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it's an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
本指南围绕 Dolibarr ERP/CRM 仓库中skill-doli-devmodule技能文档(SKILL.md)展开,系统梳理外部模块开发的强制性规则、数据库抽象层用法、Extrafields 扩展字段操作模式、代码规范与测试验证流程,并结合仓库真实源码(如 extrafields.class.php、commonobject.class.php、geturl.lib.php)进行逐一印证。读完本文,你将掌握一套可直接落地的 Dolibarr 模块开发、调试与提交规范,并能在开发新功能时正确使用$db抽象层、GETPOST*输入函数、Hook 机制与CommonObject内置方法。
1. Skill 与 AGENTS.md 的关系:规则的分层与优先级
SKILL.md是面向 AI Agent 与开发者的核心技能文档,其定位是对 AGENTS.md 的补充,二者共同构成 Dolibarr 代码库的开发准则:
- AGENTS.md 规定项目级通用规则与工作流;
- SKILL.md 聚焦于外部模块开发、数据库查询与最佳实践;
- 除非显式声明,两份文件的规则同时生效;
- 当二者冲突时,以 AGENTS.md 为准。
这一分层设计意味着:任何新代码首先必须满足 AGENTS.md 的硬性约束(如不破坏 PHP 函数/方法兼容性、不引入未经验证的外部依赖、Action/View 分离),在此之上再遵循 SKILL.md 提供的模块开发细化指引。仓库根目录同时存在 AGENTS.md 与模块模板自带的 htdocs/modulebuilder/template/AGENTS.md,可见规则已随模块脚手架一同下发。
2. 不可违反的关键规则(Critical Rules)
SKILL.md 开篇列出十条一旦违反即视为失败的硬规则,每一条都能在仓库源码或既有约定中找到落点:
| 规则 | 要点 | 仓库佐证 |
|---|---|---|
| 函数/方法兼容性 | 不破坏既有 PHP 函数与方法签名 | AGENTS.md 同款条款 |
| 依赖准入 | 不引入未经验证的外部依赖 | 核心模块内依赖均集中在htdocs/includes/ |
| Action/View 分离 | 页面动作逻辑与渲染逻辑分区 | 各模块页面统一使用/* Actions */、/* Views */注释段 |
| URL 请求 | 禁止原生 curl,改用getURLContent() | geturl.lib.php |
| 原生函数替代 | time()→dol_now()、strtolower()→dol_strtolower()等 | functions.lib.php |
| Hook 优先 | 核心生命周期事件优先挂 Hook | $hookmanager->executeHooks(...)模式 |
| 命名约定 | 尊重现有命名习惯,语言 Key 使用 PascalCase | 各模块langs/en_US/*.lang |
| 表前缀 | 所有数据表必须使用llx_前缀 | llx_mymodule_myobject.sql |
| 提交纪律 | 未经用户明确指示不得 commit / push | AGENTS.md Git Workflow 章节 |
关于原生函数替代,源码给出了明确对应关系,例如dol_now()定义于 functions.lib.php,dol_strtolower()/dol_strtoupper()定义于同文件第 2444/2461 行,dol_substr()定义于第 4381 行。这些包装函数在内部完成时区、字符编码与多字节安全处理,是跨环境一致性的基础。
3. 核心原则:不可协商的强制规则
3.1 安全与数据完整性
数据库抽象层(必须遵守):所有数据库交互只能使用 Dolibarr 数据库抽象层,页面中用全局$db,类中使用$this->db;严禁直接使用 PDO、MySQLi 或 CLI 直连。这保证了 Dolibarr 在 MySQL/MariaDB/PostgreSQL 等数据库间的可移植性。
输入输出转义:进入 action 处理器作用域后立即校验所有 GET/POST 输入;SQL 中的用户字符串一律$db->escape();整数显式(int) $var,浮点(float) $var,从根上杜绝 SQL 注入。动态 SQL 的完整查询字符串变量必须使用清晰前缀(如$sqlWhereClause、$queryParams),便于静态分析工具识别危险赋值。
3.2 代码结构与质量
- PSR-12 编码规范:所有新增与修改代码必须符合 PSR-12,可用
phpcbf/phpcs强制校验;缩进使用TAB而非空格(见 AGENTS.md PHP Best Practices)。 - PHPDoc 完整:所有属性、函数参数与返回值需要详细 PHPDoc(如
array<string,array{key1?:?type,...}>这类集合形状标注);视图文件中预期存在的变量既需要 PHPDoc 声明,又需要在文件头部附近使用'@phan-var-force';声明,以配合静态分析。 - 注释与命名用英文:所有注释、内部变量与函数名一律英文,提交前须将既有非英文文本翻译为英文。
- 提交原子性:对存量代码的合规性改进(规则 1-3)与功能演进/修复分开提交;backport 到稳定分支的代码不适用上述改造规则。
3.3 工作流与架构
- PHP 版本策略:核心与修复代码要求 PHP 7.1+;新建外部模块应面向PHP 8.1+,且每个 PHP 文件以
declare(strict_types=1);开头。 - Action/View 分离:页面 POST 动作逻辑与纯 HTML 渲染严格分离。
- Hook 优先:在实现任何运行于核心生命周期事件(如表单保存、对象更新)的逻辑前,先检查是否已有对应 Hook;标准调用模式为
$hookmanager->executeHooks('actionName', $parameters, $object, $action);,该方法定义于 hookmanager.class.php。
4. 工作流与任务指引
4.1 代码调研与数据库分析
- 用
git grep代替find:find会遍历.git目录导致极慢,推荐:git grep -n "pattern" -- "*.php" git grep -n "function_name" htdocs/core/lib/ -- "*.php" - 数据流追踪从
$db->prefix()开始:所有表名使用动态前缀获取器,如$db->prefix() . 'actioncomm',取代对MAIN_DB_PREFIX这类历史常量的依赖(见 SKILL.md 数据库常量表)。 - 依赖检查:修改文件前先在
htdocs/core/lib/与htdocs/core/class/中检索是否已有同类方法或工具;优先确认业务对象是否继承CommonObject(commonobject.class.php),复用它内置的fetch()、create()、update()、delete()等方法。 - 参数顺序核对:Dolibarr 中同名函数的参数顺序并不一致,调用前务必查看函数签名。
- 命令行访问数据库:使用 PHP 脚本方式(注意引入的是
master.inc.php而非main.inc.php):php <<'EOPHP' <?php error_reporting(E_ALL); ini_set('display_errors', 1); require_once 'htdocs/master.inc.php'; // *NOT* main.inc.php global $db; $result = $db->query('SELECT * FROM ' . $db->prefix() . 'actioncomm'); print_r($result); EOPHP
4.2 模块开发起点:modulebuilder 模板
启动新模块时,以 htdocs/modulebuilder/template/ 的目录结构为权威参考。模板包含完整骨架:core/modules/modMyModule.class.php(模块主类)、class/、lib/、sql/、tpl/、admin/、langs/en_US/、triggers/、test/phpunit/等,与 AGENTS.md 期望的模块架构(htdocs/mymodule下含core/、class/、lib/、sql/、tpl/、admin/)完全一致。新增功能若与核心进程交互,先查既有 Hook,以最小化架构影响、保持兼容性。
4.3 数据库交互细节
- 读操作:
$db->query('SELECT ...')后使用$db->fetch_object()/$db->fetch_array()取结果; - 写操作:在模块专属 action 处理器内处理表单提交,所有更新都经数据库抽象层执行;
- 建表与索引的 SQL 脚本放在
htdocs/install/mysql/tables/,可参照既有文件; - 列表过滤 WHERE 子句优先用
natural_search($fields, $value, $mode)(定义于 functions.lib.php)拼装,而非手工拼接 LIKE 条件。
5. Extrafields 扩展字段最佳实践
Extrafields(扩展字段)是 Dolibarr 为任意CommonObject业务对象动态追加自定义字段的机制,本技能文档给予了最详细的篇幅。
5.1 加载与访问
$object->fetch(); // CommonObject fetch 会顺带加载 extrafields // 通过 array_options 访问字段值: $field_copy = $object->array_options['options_FIELDNAME']fetch()加载扩展字段的逻辑封装在 commonobject.class.php 的insertExtraFields()(第 7017 行)与showOptionals()(第 9643 行)等配套方法中。
5.2 保存
在调用$object->create()或$object->update()之前确保扩展字段已赋值:
// 表单提交场景:从 POST 提取可选字段 $ret = $extrafields->setOptionalsFromPost($extralabels, $object); // 直接赋值场景: $object->array_options['options_FIELDNAME'] = $value; // update() 会通过 insertExtraFields() 自动保存扩展字段 $result = $object->update($user);setOptionalsFromPost()定义于 extrafields.class.php,负责按$extralabels配置批量清洗并写入array_options。
5.3 展示
- 视图页:
print $object->array_options['options_FIELDNAME']; - 编辑页(联动 Hook 与 showOptionals):
$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); if (empty($reshook) && !empty($extrafields->attribute_label)) { print $object->showOptionals($extrafields, 'edit'); }
5.4 扩展字段表结构
每个对象类型拥有独立的 extrafields 表,命名与结构固定:
llx_{objecttype}_extrafields - rowid (AUTO_INCREMENT PRIMARY KEY) - tms (timestamp) - fk_object (integer NOT NULL) - import_key (varchar)模板仓库中提供了对应建表脚本:llx_mymodule_myobject_extrafields.sql 与索引脚本 llx_mymodule_myobject_extrafields.key.sql。
5.5 检查并动态创建扩展字段
ExtraFields 类没有“检查字段是否存在”的方法,需使用如下模式(extrafields.class.php 提供fetch_name_optionals_label()于第 1071 行、addExtraField()于第 181 行):
global $db; $extrafields = new ExtraFields($db); $extralabels = $extrafields->fetch_name_optionals_label($elementtype); // 跟踪扩展字段配置 $my_fields = array( 'custom_name_1' => array( 'label' => 'CustomLabel1', 'type' => 'varchar', 'size' => '64', 'enabled' => '1' ), 'custom_name_2' => array( 'label' => 'CustomLabel2', 'type' => 'url', 'size' => '255', 'enabled' => '1' ), ); foreach ($tracking_fields as $name => $config) { // 检查扩展字段是否存在,不存在则创建 if (!isset($extralabels[$name])) { // 为静态分析提供具名参数 $pos = 0; // 0 = 自动排序 $unique = 0; $required = 0; $default_value = '0'; $param = ''; $alwayseditable = 0; $perms = '0'; $list = '0'; // '0' = 列表页永不显示 $help = ''; $computed = ''; $entity = ''; $langfile = ''; $enabled = $config['enabled']; $result = $extrafields->addExtraField( $name, $config['label'], $config['type'], $pos, // pos(0 = 自动) $config['size'], $elementtype, $unique, // unique $required, // required $default_value, // default_value $param, // param $alwayseditable, $perms, // perms $list, // list('0' = 列表不可见) $help, $computed, $entity, $langfile, $enabled ); } }addExtraField()的完整签名还支持$totalizable、$printable、$moreparams、$aiprompt、$emptyonclone、$showintooltip、$personal_data等后置参数,可用于金额合计、可打印、克隆置空、工具提示等进阶场景(extrafields.class.php)。
6. 输入处理与通用配置访问
6.1 GETPOST 系列类型安全输入函数
禁止直接使用$_GET/$_POST,一律使用以下函数获得类型转换与转义:
| 函数 | 类型 | 示例 | 说明 |
|---|---|---|---|
GETPOST($param, $type) | Mixed | GETPOST('id', 'int') | 返回 GET 或 POST 值并做类型转换 |
GETPOSTINT($param) | Integer | GETPOSTINT('socid') | GETPOST($param, 'int')的简写 |
GETPOSTARRAY($param) | Array | GETPOSTARRAY('selected') | 用于多选输入 |
最佳实践:整数(ID、计数等)用GETPOSTINT(),其余场景用GETPOST()指定类型。AGENTS.md 还补充了GETPOSTDATE()、GETPOSTFLOAT()等变体,覆盖日期与金额输入。
6.2 配置读取与模块启用判断
- 配置读取:使用
getDolGlobalString()/getDolGlobalInt()/getDolGlobalBool(),而不是直接访问$conf->global->XXX(分别定义于 functions.lib.php 与第 359 行); - 模块启用判断:使用
isModEnabled('module'),而不是!empty($conf->module->enabled)(定义于 functions.lib.php); - 金额解析用
price2num()(functions.lib.php),金额展示用price(),不要用number_format()或裸类型转换。
6.3 日期处理约定
- PHP 内存中的带时间日期一律为UTC 日期;
- 用户输入日期转 UTC:
GETPOSTDATE('datefieldname', '', 'tzuserrel')(仅日月年时用'getpost'模式); - 写 SQL 用
$db->idate()(PHP 时间戳 → SQL),读 SQL 用$db->jdate()(SQL → PHP 时间戳); - 使用
dol_now()、dol_print_date()、dol_mktime()而非time()、date()、mktime()。
7. HTML 渲染、日志、文件系统与安全
7.1 HTML 渲染函数
优先使用 html.lib.php 中带@example注释的渲染函数,而非裸echo/print:
| 类别 | 函数 | 示例 |
|---|---|---|
| 文本输出 | dolPrintLabel($s) | <span><?php echo dolPrintLabel($object->name); ?></span> |
| 文本输出 | dolPrintText($s) | <div class="description"><?php echo dolPrintText($object->description); ?></div> |
| HTML 输出 | dolPrintHTML($s) | <div class="rich-text"><?php echo dolPrintHTML($object->note); ?></div> |
| 属性值 | dolPrintHTMLForAttribute($s) | <span title="<?php echo dolPrintHTMLForAttribute($tooltip); ?>">?</span> |
| 文本域 | dolPrintHTMLForTextArea($s) | <textarea><?php echo dolPrintHTMLForTextArea($content); ?></textarea> |
| 图标 | img_picto($alt, $picto) | <?php echo img_picto('Edit', 'edit'); ?> |
| 按钮 | dolGetButtonAction($label, $text, $type) | <?php echo dolGetButtonAction('Save', '', 'default'); ?> |
| 消息 | setEventMessages($msg, $msgs) | setEventMessages('Saved successfully', array('Message 1', 'Message 2')) |
| 格式化 | yn($yesno) | <?php echo yn($obj->active); ?> |
| 格式化 | dolOutputDates($start, $end) | <?php echo dolOutputDates($date_start, $date_end); ?> |
选择依据:单行纯文本用dolPrintLabel,多行纯文本用dolPrintText,带允许标签的富文本用dolPrintHTML,属性值一律dolPrintHTMLForAttribute;图标使用img_*系列,按钮使用dolGetButton*,用户反馈使用setEventMessages。
7.2 日志与调试
- 统一使用
dol_syslog()打日志,并指定级别LOG_DEBUG/LOG_WARNING/LOG_ERR; - 已提交代码不得残留
var_dump()、print_r()或die(); - 用户可见消息用
setEventMessages()。
7.3 文件系统包装函数
移动、删除、创建目录一律使用 Dolibarr 原生函数:dol_move()、dol_delete_file()/dol_delete_dir()/dol_delete_dir_recursive()、dol_mkdir()、dol_copy()、dol_is_file()/dol_is_dir();任何用户提供的文件名必须用dol_sanitizeFileName()/dol_sanitizePathName()清洗,绝不使用裸 PHPmkdir()/unlink()/file_exists()。
7.4 安全基线
- 页面访问控制:
restrictedArea($user, 'module', $id, 'table'),或使用accessforbidden()拒绝; - 用户输入:一律经
GETPOST()系列加载; - JS 注入防护:PHP 生成字符串用
dol_escape_js()转义; - SQL 注入防护:
$db->escape()或(int)/(float)强转; - XSS 防护:HTML 输出用
dolPrintHTML()/dolPrintHTMLForAttribute(); - CSRF:POST 表单加
<input type="hidden" name="token" value="'.newToken().'">;带修改性action的 GET 链接追加...&token='.newToken().';Ajax 调用用currentToken()并让 ajax 端点声明NOTOKENRENEWAL;无会话的公开端点(如 webhook)通过页面级NOCSRFCHECK或全局$dolibarr_nocsrfcheck豁免。
8. 国际化(i18n)
- 用户可见字符串一律
$langs->trans('Key');结果用于 HTML 转义函数时用$langs->transnoentities(); - 语言文件位于
mymodule/langs/en_US/,不要修改或翻译其他 locale 文件(由外部工具统一管理); - 语言 Key 使用 PascalCase(如
MyModuleLabel); - 页面顶部加载语言文件:
$langs->load('mymodule@mymodule'); - 注释、变量名、函数名一律英文。模块模板的语言文件示例见 mymodule.lang。
9. 测试与验证流程
9.1 提交前验证
- 校验工作流:确认 Create → Edit → Delete 全流程被改动正确处理;
- 生成测试用例:给出最小化的单元测试,或列出交互式测试脚本步骤,覆盖空输入、权限失败等边界场景。
9.2 自动化检查工具
- pre-commit:本地安装 git hook 后运行
pre-commit run php-cbf --files RELATIVEFILEPATH等(包含 php-cbf、php-cs、shellcheck、php-lint); - PHPUnit:重大改动或重要新函数,须在 test/phpunit/ 增加或更新测试文件,并确保登记进 AllTests.php;模板自带 MyObjectTest.php 与 MyModuleFunctionalTest.php 可参考;
- phpstan:需验证时加参数
-a dev/build/phpstan/bootstrap_action.php; - phan:默认不使用(过慢),如被显式要求则加参数
-k .phan/config.php -B dev/tools/phan/baseline.txt --quick。
9.3 权限与多实体验证
- 权限检查:
$user->hasRight("module", "permission")或三级形式$user->hasRight("module", "objectname", "permission"); - 多实体兼容:SQL 查询补
AND entity IN ('.getEntity("tablename").')。
10. Git 工作流与提交规范
- 未经用户明确要求,不得 commit 或 push(这是 SKILL.md 与 AGENTS.md 共同强调的规则);
- 分支策略:每个大版本一个分支(只做修复),
develop分支同时承载修复与新特性; - 提交信息格式:
TYPE: #issueNumber Short description,类型取NEW、FIX、CLOSE、QUAL、PERF、UIUX(大写,以便进入 ChangeLog),示例:FIX: #1234 Correct VAT calculation on credit notes; - 不要手动更新 ChangeLog(发布前由维护者从提交标题自动生成);
- PR 内容单一:只能包含数据库结构变更、或一个新特性、或一个 bug 修复、或一次重构,严禁混装;稳定分支(非 develop)的 PR 一次只能含一个 bug 修复;
- 提交首行不超过 70 字符,描述末尾以
Generated by或Co-authored-by:提及 AI 代理名称;PR 描述不超过 80 行。
11. 一句话清单(快速自查)
开发任何 Dolibarr 外部模块时,可对照以下清单逐项自查:
- 页面动作在
/* Actions */,渲染在/* Views */; - 数据库只用
$db/$this->db,SQL 字符串变量带$sql_前缀,值经escape()或类型强转; - 表名一律
llx_前缀 +$db->prefix()动态拼接; - 用户输入只用
GETPOST()/GETPOSTINT()/GETPOSTARRAY(); - 配置用
getDolGlobalString/Int/Bool(),模块启用用isModEnabled(); - 生命周期逻辑优先查 Hook:
$hookmanager->executeHooks(...); - 业务对象尽量继承
CommonObject,复用fetch/create/update/delete; - Extrafields 用
setOptionalsFromPost()+array_options['options_*']+insertExtraFields()链路; - 日期/大小写/字符串用
dol_*包装函数,URL 请求用getURLContent(); - 日志用
dol_syslog(),消息用setEventMessages(),输出用dolPrint*系列; - 注释、变量、语言 Key 一律英文,语言文件只维护
en_US; - 提交前验证 Create/Edit/Delete、权限与多实体,未获明确指令不 commit / push。
按此清单开发,即可在兼容核心架构的同时,获得静态分析、测试与社区评审的一致认可。
- 企业应用
- 后端
【免费下载链接】dolibarr
Dolibarr ERP CRM is a modern software package to manage your company or foundation's activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it's an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
相关推荐
终极Transformer源码解析:从模块化设计到实战最佳实践
终极Transformer源码解析:从模块化设计到实战最佳实践 Transformer作为自然语言处理领域的革命性模型,其"注意力机制就是一切"的创新理念彻底改
人工智能NLP深度学习geektime-downloader项目架构深度剖析:模块化设计与最佳实践指南
geektime downloader项目架构深度剖析:模块化设计与最佳实践指南 引言:极客时间资源下载的技术挑战 你是否曾经遇到过这样的困境:购买了极客时间的
CLI终极指南:Gin框架深度剖析与最佳实践——从源码到高性能Web开发
终极指南:Gin框架深度剖析与最佳实践——从源码到高性能Web开发 Golang作为近年来备受关注的编程语言,凭借其高效的并发模型和简洁的语法,在Web开发领域
人工智能AI 应用AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考