news 2026/9/28 8:43:51

Dolibarr 外部模块开发最佳实践:Skill 指南全解与源码级深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Dolibarr 外部模块开发最佳实践:Skill 指南全解与源码级深度剖析
  • 企业应用
  • 后端

【免费下载链接】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.

项目地址:https://gitcode.com/gh_mirrors/do/dolibarr
点击查看免费下载

本指南围绕 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 / pushAGENTS.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)MixedGETPOST('id', 'int')返回 GET 或 POST 值并做类型转换
GETPOSTINT($param)IntegerGETPOSTINT('socid')GETPOST($param, 'int')的简写
GETPOSTARRAY($param)ArrayGETPOSTARRAY('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 提交前验证

  1. 校验工作流:确认 Create → Edit → Delete 全流程被改动正确处理;
  2. 生成测试用例:给出最小化的单元测试,或列出交互式测试脚本步骤,覆盖空输入、权限失败等边界场景。

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 外部模块时,可对照以下清单逐项自查:

  1. 页面动作在/* Actions */,渲染在/* Views */;
  2. 数据库只用$db/$this->db,SQL 字符串变量带$sql_前缀,值经escape()或类型强转;
  3. 表名一律llx_前缀 +$db->prefix()动态拼接;
  4. 用户输入只用GETPOST()/GETPOSTINT()/GETPOSTARRAY();
  5. 配置用getDolGlobalString/Int/Bool(),模块启用用isModEnabled();
  6. 生命周期逻辑优先查 Hook:$hookmanager->executeHooks(...);
  7. 业务对象尽量继承CommonObject,复用fetch/create/update/delete;
  8. Extrafields 用setOptionalsFromPost()+array_options['options_*']+insertExtraFields()链路;
  9. 日期/大小写/字符串用dol_*包装函数,URL 请求用getURLContent();
  10. 日志用dol_syslog(),消息用setEventMessages(),输出用dolPrint*系列;
  11. 注释、变量、语言 Key 一律英文,语言文件只维护en_US;
  12. 提交前验证 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.

项目地址:https://gitcode.com/gh_mirrors/do/dolibarr
点击查看免费下载

相关推荐

上一篇:5分钟掌握Super IO:Blender最高效的剪贴板导入导出插件
下一篇:Agent-Native 智能体应用开发指南:从跑通第一个模板到能改、能上线

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 8:43:28

基于YOLOv8的道路标线磨损监测系统:数据训练到部署全流程

简介&#xff1a;一套基于YOLOv8的交通道路标线磨损监测系统&#xff0c;面向目标检测、深度学习的毕设与课程设计场景&#xff0c;也适合计算机相关专业学生快速上手实战。资源包含完整源码、可视化交互界面、标注数据集与部署说明&#xff0c;运行环境简单&#xff0c;下载后…

作者头像 李华
网站建设 2026/9/28 8:42:43

操作系统核心原理与实战:从进程、内存到文件系统的完整认知体系

写这篇东西的起因很简单——我带过不少新人&#xff0c;也带过不少刚转行做开发的朋友&#xff0c;发现大家问得最频繁、卡壳最多的往往是同一个问题&#xff1a;操作系统到底在做什么&#xff1f;这个词咱们每天都在用&#xff0c;电脑上跑着 Windows&#xff0c;服务器上跑着…

作者头像 李华
网站建设 2026/9/28 8:42:43

MATLAB实现RF-RFE-BP回归预测:随机森林特征筛选与神经网络结合

做回归预测的人&#xff0c;多少都被“特征太多”折腾过。手里一份多维数据集&#xff0c;特征几十个上百个&#xff0c;直接扔给BP神经网络&#xff0c;经常出现两种结果&#xff1a;要么训练半天收敛不动&#xff0c;要么训练集拟合得漂亮&#xff0c;测试集一测就拉胯。问题…

作者头像 李华
网站建设 2026/9/28 8:42:19

Univer SDK:国产开源文档协同引擎技术解析

1. Univer 是什么&#xff1a;一个被严重低估的国产办公套件底层引擎最近在几个技术群里看到有人问“Univer 在线怎么接入”“Univer SDK 文档在哪找”&#xff0c;还有人把 Univer 和阿里云认证 SDK、Android SDK、Vivado SDK 混在一起搜&#xff0c;甚至搜出“hip sdk 安装包…

作者头像 李华
网站建设 2026/9/28 8:41:40

Vue3 SFC中TypeScript编译报错全解析:从原理到排查

启动 Vue3 项目时看到ERROR in ./src/components/CompositionDebounce.vue?vue&typescript&langts&#xff0c;这行报错我在不同项目里碰到过不下十次。第一次遇到时我也被那串长路径唬住了&#xff0c;以为是什么平台特殊性错误&#xff0c;后来才看明白——它就是 V…

作者头像 李华
网站建设 2026/9/28 8:38:52

DAPLink脱机烧录原理与STM32/AT32量产实战指南

1. 项目概述&#xff1a;为什么DAPLink脱机烧录值得你花两小时彻底搞懂DAPLink不是一块简单的USB转SWD调试器&#xff0c;它是ARM官方开源的、被全球嵌入式开发团队深度定制的固件级烧录中枢。我第一次在产线看到它批量刷写300台STM32F103C8T6时&#xff0c;烧录速度比传统ST-L…

作者头像 李华