语言和开发规则

From Dolibarr ERP CRM Wiki
Jump to navigation Jump to search


以下是我们在Dolibarr项目中使用的开发语言、语法和开发标准的一些规则:

版本管理

  • Dolibarr 必须适用于:
  1. 所有操作系统(Windows, Linux, MACOS...)
  2. PHP 7.1.0+(需要DateTimeZone.getOffset、php-intl等函数)(除了数据库访问模块之外,必须可以在没有任何PHP扩展模块的情况下运行)。
  3. Mysql 5.1+

规范版权声明

  • 所有 PHP 文件必须以如下所示的标题开头。
<?php
/* Copyright (C) YYYY John Doe  <email@email.com>
 *
 * Licence information
 */
...

当编辑项目的现有文件时,必须在他人的版权行下添加自己的版权行。

规范 PHP 代码

PHP

  • Dolibarr 是用PHP编写的,支持所有高于 7.1.0+的 PHP 版本(需要 DateTimeZone.getOffset、 php-intl等函数)。 所有文件的扩展名必须为 .php
  • 对PHP超全局变量的调用必须通过专用运算符,如:$_COOKIES、$_SERVER、$_ENV。但是要获取$_GET或$_POST的内容,必须使用 Dolibarr 函数 GETPOST()GETPOSTINT()

在PHP中被弃用的其他运算符($HTTP-SERVER-GET,…)不能再使用。因此,即使选项 register-long-arrays 设置为off,代码也必须能正常工作。此外,当PHP选项 register-globals 设置为off(PHP推荐)或 register-globals 设置为on(许多安装的默认设置)时,代码也必须能正常工作。

  • 请勿使用 PHP_SELF,请改用 $_SERVER["PHP_SELF"]。此外,Dolibarr框架调用了一个例程,以使$_SERVER["PHP_SELF"]的内容更安全(在main.inc.php文件中,因此是在任何业务处理之前)
  • 当多个变量必须初始化为相同的值时,必须使用多个单独的声明(由;分隔)语句:
$var1=1;$var2=1;$var3=1;

而不是

$var1=$var2=$var3=1;

后一种情况下更慢。

其效率较低。

  • 字符串中调用变量时,变量必须在引号之外。
print 'My text show my '.$variable.' !';
  • 注释必须遵循C语法,即对单行注释使用双斜杠,对代码块注释使用斜杠星号。
/* 块注释
 *
 * 块注释结束
 */

$monobjet = new MonObjet($db);
$result=$monobjet->fetch($idobject);

for ($i = 1 , $i < 2 ; $i++)
{
  // 单行注释
  print $i;
}
  • 函数必须在成功时返回大于等于0的数字,在错误时返回小于0的数字。
  • Dolibarr核心代码中没有死代码(不被使用)(仅由外部模块使用的代码必须包含在外部模块中)。
  • 使用"include_once"加载任何包含函数或类定义的文件(如 *.class.php 和 *.lib.php),使用 "include"加载任何模板文件或包含HTML和PHP混合代码的文件(如 *.inc.php 和 *.tpl.php)。
  • 要使用的编码风格是PSR-12 (https://www.php-fig.org/psr/psr-12/)。仅强制执行“必须”的规则。另请注意以下要点/例外:
    • 行的长度:PSR-12规定行长度的“软”限制为120个字符。事实上,最好是用长行声明数据而不是长页面,因为该代码不包含任何有趣或有用的逻辑,但“硬”限制为1000个字符。超出此范围的代码将导致集成平台的持续报错。
    • 允许使用制表符:另一个例外是我们没有系统地用空格替换制表符。使用制表符对大多数编辑器/开发人员来说更为方便。此外,使用空格也会破坏某些自动格式化功能(如某些版本的Eclipse的自动格式化功能)。目前,最好的设置是“保持空格/制表符不变”,但是,您可以启用“删除行尾的空格”选项。
    • 注意 1:遵循以下规则尤为重要:
      • 文件必须用Unix格式(LF)保存,而不是Windows(CR/LF)格式。Unix格式兼容所有操作系统,如Unix、Windows、Mac,但是Windows文本文件格式在Unix下的某些PHP中会出现问题。
      • 不要使用PHP智能标签。PHP代码部分必须以 <?php 开头
    • 注意 2:您可以使用文件 dev/setup/codesniffer/ruleset.xml 作为规则文件,使用 PHPCodeSniffer 检查代码规范。
    • 注意 3:您可以使用文件 dev/setup/eclipse/PSR-12 [built-in].xml 作为规则文件来配置 Eclipse 的语法格式化。
    • 注意 4:您会注意到,当前代码不一定遵循所有这些规则。原因是历史性的。这条规则是在这个过程中制定的,所以如果你发现任何代码不符合这个标准,不要犹豫,纠正它。(请记住2个例外)。

类和属性结构

在不同的类中有一些相同的属性。为了避免使用不同的名称,我们将使用以下PHP属性名称:

- entity                         that is id for the multicompany feature
- date_creation:                 date of creation
- date_modification:             date of last modification (often field tms in database)
- date_validation:               date validation
- fk_user_creation:              id of User of creation
- fk_user_modification:          id of User of last modification
- fk_user_validation:            id of User of validation
- import_key                     contains the import code YYYYMMDDHHMMSS, if record was loaded with a mass import.

- entity 多公司功能的ID

- date_creation: 创建日期

- date_modification: 上次修改日期(通常是数据库中的tms)

- date_validation: 确认日期/验证日期

- fk_user_creation: 创建者的ID

- fk_user_modification: 上次修改者的ID

- fk_user_validation: 确认者/验证者的ID

- import_key 如果记录是用批量导入加载的,则存放导入编号 YYYYMMDDHHMMSS 。

规范 SQL

DDL 文件格式

每张表必须有两个包含数据库结构定义(DDL)的文件。

  • 第一个文件定义表及其字段。文件名包含表名,例如:llx_mytable.sql

还将为每个字段添加注释以解释其含义。

  • 第二个文件定义所有外键、性能索引或其他唯一性约束,文件名类似于:llx_mytable.key.sql

对于所有标准模块,这些文件必须存储在目录 install/mysql/tables 中;对于外部模块,这些文件必须存储在目录 mymodule/tables 中。

示例:用于创建表 llx_mytable 的文件 llx_mytable.sql

-- ===========================================================================
-- Copyright (C) 2013 Author <email@author.com>
-- 
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
-- ===========================================================================

create table llx_mytable
(
  rowid       integer NOT NULL AUTO_INCREMENT PRIMARY KEY,
  ref	      varchar(30)       NOT NULL,		-- object reference number
  entity      integer DEFAULT 1 NOT NULL,	        -- multi company id
  ref_ext     varchar(255),-- reference into an external system (not used by dolibarr)
  field_one   integer,
  field_two   integer NOT NULL,
  fk_field    integer,
  field_date  datetime,
  datec       timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,,   -- creation datetime
  tms         timestamp,    -- update time stamp
  fk_user_author integer NOT NULL,                     -- Author, foreign key of llx_user
  fk_user_mod    integer NOT NULL,                      -- Last updater, foreign key of llx_user
  import_key	varchar(14)                -- Use by import process
)type=innodb;

示例:用于为表 llx_mytable 创建主键/索引的文件 llx_mytable.key.sql

-- ===========================================================================
-- Copyright (C) 2013 Author <email@author.com>
-- 
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
-- ===========================================================================

ALTER TABLE llx_mytable ADD UNIQUE uk_mytable_field (field_one, field_two);

ALTER TABLE llx_mytable ADD CONSTRAINT fk_mytable_fk_field FOREIGN KEY (fk_field) REFERENCES llx_matablepere (rowid);

表和字段的结构

  • 所有表都有前缀,用以避免与其它项目的命名冲突。目前,前缀可在安装时更改。其默认值为 llx_
  • 表的结构

当您创建一个新表时,建议使用与其他Dolibarr表相同的命名约定。必须要有以下字段:

- rowid INTEGER AUTO_INCREMENT PRIMARY KEY    技术ID
- entity INTEGER default 1                    多公司功能的ID
- date_creation   datetime                    创建日期
- tms             timestamp                   上次修改日期(数据库自行管理,无需通过代码管理)
- import_key                                  如果数据是批量导入的,则保存导入编号YYYYMMDDHHMMSS
- status          smallint                    存储对象的状态

可能字段:

- fk_user_creat integer 创建者的ID(或者是 fk_user_author)
- fk_user_modif integer 修改者的ID
- fk_user_valid integer 确认者/验证者的ID(如果适用)
- fk_soc        integer 合伙方的ID(如果适用)

备注字段:

- note_private text 对象的私有备注
- note_pubic   text 对象的公开备注

或者

- note text 备注(如果不需要区分私有或公开)
  • 字段类型:

在实践中,为了与所有国家/地区对金额要求的任何准确性兼容、与任何数据库语法兼容、以及与Dolibarr升级框架兼容,我们将仅使用以下类型:

- integer		        用于任何整数、ID或外键(bigint 可用于非常大的表ID)
- smallint                      用于任何小尺寸的整数(如存储状态代码)或布尔值
- double(24,8)			用于任何金额
- double(6,3)			用于增值税税率
- real				用于数量
- varchar			用于字符串(包括长度为1的字符串),char类型已被逐渐弃用
- timestamp			用于要自动更新的日期+时间字段
- datetime			用于日期+时间字段
- date				用于日期字段
- text or medium text		用于大字段 (此类字段上不允许有索引)

所有的业务规则必须位于同一个位置,是在PHP代码中,而不是在客户端或数据库中,这就是为什么 enum 类型也不允许使用。

由于兼容性的原因,其他类型是不允许使用的。

主键

表的主键必须是 rowid

目前有一些旧表不符合此规则(如 表llx_c_actioncomm 的主键是id),这是历史原因,这种情况不应该再发生。

外键

外键的名称必须以前缀 fk_ 开头,后跟表名(即引用表的名称)(这是必需的,以避免项目中的名称冲突。因为在某些DBMS中,如Postgresql中,即使是不同的表中也不允许名称重复),再后跟字段名(即引用字段的名称)(这是为了允许在同一表中有多个外键)。

示例:fk_facture_fourn_fk_soc 是表 llx_facture_fourn 中的外键,建在字段 fk_soc 上(引用另一个表中的 rowid 字段)

注意:如果您正在开发自己的外部模块,则您的表中不应该有指向 Dolibarr 标准模块表的外键。因为这将破坏 Dolibarr 的升级,修复,备份和还原工具,也可能破坏标准模块的功能。

替代键

有时,我们需要主键以外的其它唯一索引。因此,还可以创建一个替代主键的唯一索引。这样的索引名称以前缀 uk_ 开头,后跟表名(这是必需的,以避免项目中的名称冲突。因为在某些DBMS中,如Postgresql中,即使是不同的表中也不允许名称重复),再后跟描述键的后缀(这是为了允许在同一表中有多个唯一索引)。

示例:uk_societe_code_client 是表 llx_societe 在 code_client 字段上的一个唯一索引

性能索引

某些字段通常用作搜索、排序或连接条件。在这种情况下,应定义性能索引用以提高性能。此类索引将以前缀 idx_ 命名,后跟表名、再后跟索引所在字段的名称。

示例:idx_societe_user_creat 是表 llx_societe 在字段 user_creat 上的性能索引。

SQL编码规则

  • 使用别名/字段命名

在进行 select 时,可以使用别名来简化查询的编写和阅读:

select chp1, chpxxx2 as chp2 from table2 as t1, table2 as t2 where t1.chpx = t2.chpy

但是,不应在 update 请求中使用别名,因为它们与 MySQL 3.1 不兼容。

  • 禁止使用 SELECT * !每个 SELECT 都必须指定要检索的字段的完整列表。这有助于避免混淆。最重要的是,这使代码的重新生成更加容易,并使变更对字段的影响分析成为可能。示例:
SELECT field_a, field_b, field_c FROM table_1 WHERE field_d = '$id'
  • 在 SQL insert 时,必需给字段加引号,但不包括要存储在 double 或 real 类型字段中的金额数字。浮点数字上的引号有时会导致其存储了不同的值。例如,如果字段类型为 double(24,8),则插入412.62时实际上是将值412.61999512(由于隐式的字符串 - 数字转换)存储到数据库中。只有PHP可以看到412.61999512。其他工具将看到412.62,这使其看上去没有问题。但PHP是正确的,数据库中确实存在错误的值。通过删除数字上的引号,就不会出现此问题。

示例:

Good:     INSERT INTO table_1 (field_txt, field_num) VALUES ('txt', 412.62)
Bad:      INSERT INTO table_1 (field_txt, field_num) VALUES ('txt', '412.62')

请注意,浮点数的问题是普遍性的,不仅是在数据库访问上,而且在处理实数时,所有语言都存在浮点数问题。因此,一旦受到影响,就必须使用 price2num 函数清除浮点问题。根据需要,第二个参数设置为:“MU”(如果是单价)、“MT”(如果是总价)或“MS”(其他情况),具体取决于数字的使用情况。另请参阅下面的“实数、金额和计算”一章。

  • SQL命令中不允许使用SQL日期函数NOW、SYSDATE、DATEDIFF。如果可能的话,还必须避免使用MONTH、YEAR。如果必须在字段中使用当前日期,则该值必须来自PHP,例如通过 dol_now() 函数,而不是来自数据库引擎。原因如下:
    • 为了更好的代码可移植性
    • 正确管理TimeZone,并且不依赖于数据库的TimeZone(参考时区是PHP时区,数据库时区可能与此不同,因此日期转换必须在PHP端进行)。
    • 更好的性能(请参阅下面的示例和评论)

示例,不要:

$sql="SELECT rowid FROM table where datefield = NOW()";

要:

$sql="SELECT rowid FROM table where datefield = '".$this->db->idate(dol_now())."'";

示例,不要:

$sql="SELECT rowid FROM table where DATEDIFF(table.datefield, NOW()) > 7";

要:

$sql="SELECT rowid FROM table where datefield < '".$this->db->idate(dol_now() - (7 * 24 * 3600))."'";

此规则的另一个优点是,查询可以从索引使用中获益,因为它将日期字段与固定值进行比较。当使用 datediff 时,因在比较之前对字段进行了处理,使得数据库无法使用该字段的索引。与不使用 datediff 的方案相比,其性能非常差。

  • 禁止使用 WITH ROLLUP

不应使用此语句:不同数据库的处理方式不同。此外,使用 WITH ROLLUP 会破坏返回数据的纯度。通过该指令生成小计的中间聚合可以在PHP的帮助下轻松完成,并有助于保持返回的数据表干净(不会被人工插入的数据损坏)。除了标准化程度不高之外,通过在数据中插入计算结果,给出的结果是数据和聚合业务计算的混合结果,对代码的理解变得复杂,甚至在这些聚合上的代码演变变得复杂时更可能难以理解。

  • 使用 $db->ifsql 替代 IF SQL

不要在SQL查询中使用IF。请改用 $db->ifsql() 方法,您将创建一个与所有SQL数据库兼容的SQL IF。

  • 不使用 DELETE CASCADE 和 UPDATE CASCADE

禁止使用此类SQL语句,因为它们绕过了应用程序的业务规则,给开发人员带来了很多失控的麻烦。例如,如果表A和B之间存在 DELETE CASCADE,当应用程序运行代码删除A中的记录时,表B中的子项也将被删除。如果一个PHP Dolibarr触发器(例如,由外部模块提供)存在于B表记录的删除操作上(例如,验证删除或执行附加操作),则 DELETE CASCADE 将在不执行PHP Dolibarr触发器的情况下仅执行删除B表记录的操作,跳过了由外部模块的PHP Dolibarr触发器执行的验证或附带操作。因此,所有业务规则都必须在同一端(PHP服务器端)实现,这就是为什么不允许在数据库端实现业务规则的原因(与下一节关于数据库触发器的情况相同)。

请注意,外部模块可以将它们(DELETE CASCADE 和 UPDATE CASCADE)用于Dolibarr表及其模块表之间的链接,但最好的推荐解决方案是在删除父表的记录时使用 Dolibarr PHP 触发器来实现其子表记录的删除。

使用数据库触发器

Dolibarr的代码中不允许使用数据库触发器,因此您应该不会遇到数据库触发器的问题。

数据库触发器不可移植。在升级数据库版本时,兼容性可能会被破坏,数据库触发器通常不包含在备份工具中,数据库触发器需要您可能没有(也不应该有)的操作公共安全的数据库权限,数据库触发器还意味着将应用程序的一些“业务规则”放置在了数据库中,其中大多数其他规则由代码本身管理(这可能会产生冲突或无法使用步进调试程序调试代码),数据库触发器使用数据库的时区执行代码,而不是使用可能不同的应用程序的参考时区(生成数据库偏移量)...

我们有很多理由不建议在您自己的开发中使用数据库触发器。

使用数据库触发器可以在进行开发时节省您的时间,但这是一种非常糟糕的做法,因此被禁止,因为它与Dolibarr团队所期望的高质量代码不兼容。使用数据库触发器的外部模块在市场上也可能被拒绝,因为它不能为超过50%的用户正常工作。

基本迁移规则

结构迁移脚本是 install/mysql/migration/a.b.c-x.y.z.sql 类型的文件。在从n到n+1的迁移文件中,只有在从n+1版本迁移到n+2版本时,才会销毁在n+1中不再使用的表(drop table)。这是为了在版本升级期间始终保留过时的表,以便在发生升级错误的情况下能回退到原版本。

Mysql/MariaDB 特性

  • 表必须使用 InnoDB 格式声明

实际上,此格式支持外键、附加到外键的任何约束以及使数据在表之间具有一致性所需的事务完整性概念。

这保证了 Dolibarr 操作在表之间保存了正确的数据,即使事务修改了不同的表也是如此。

  • 即使 Mysql 的 strict 选项被激活,Dolibarr也必须能正常工作

要启用它(强烈建议在Dolibarr开发中使用),请在MySQL服务器配置文件(my.cnf 或 my.ini)中添加以下行:

sql-mode="STRICT_ALL_TABLES,ONLY_FULL_GROUP_BY,NO_ZERO_DATE,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"

PostgreSQL 特性

只需要维护 MySQL 的文件。其他数据库的文件由数据库的Dolibarr驱动程序“动态”转换。

有一个例外,就是 SQL "UPDATE FROM":

MySQL 语法:

UPDATE table_taget as target, table_source as source SET fieldtarget=source.fieldsource
WHERE source.rowid=target.rowid;

PgSQL 语法:

UPDATE table_taget as target SET fieldtarget=source.fieldsource
FROM table_source as source WHERE source.rowid=target.rowid;

在dolibarr内核中没有本地化“update from”。但对于您的模块,您应该这样做:

if ($this->db->type=='pgsql') {
$sql="UPDATE table_taget as target SET fieldtarget=source.fieldsource
FROM table_source as source WHERE source.rowid=target.rowid";
} else {
$sql= "UPDATE table_taget as target, table_source as source SET fieldtarget=source.fieldsource
WHERE source.rowid=target.rowid";
}

规范 HTML

语法规则

  • 使用的 HTML 必须符合 HTML 标准,而不是 XHTML。HTML标记中的所有属性都必须 小写 ,并使用 双引号 括起来。
  • href 链接必须是绝对路径,请使用函数 dol_buildpath() 从htdocs目录的相对路径(对于外部模块)或使用 DOL_URL_ROOT(对于核心代码)获取绝对路径。对于img标记,必须调用 img_picto() 。

示例:

print '<a href="'.dol_buildpath('/mydir/mypage.php').'">'.img_picto('Texte alt','namepictopng','').'</a>';
  • HTML tables 必须具有没有强制宽度的列,但包含非可变长度信息的列除外。例如,带有 picto 的列可以强制为with=“20px”。

在其他情况下,应避免强制列的宽度。原因是,在大多数情况下,浏览器在自动设置正确的列宽度方面比手动强制宽度做得更好。并且无论屏幕分辨率如何,它都可以正常工作。

  • 应避免在PHP页面中使用Javascript/ajax和Java脚本。但是,如果需要包含 javascript代码,则必须先对 "$conf->use_javascript_ajax" 进行条件判断:
if ($conf->use_javascript_ajax) {
...  // php code generating javascript here
}
  • 不应使用 Windows 弹出窗口,但工具提示除外(并且仍受上述条件的限制)。
  • 外部脚本是用Perl编写的,如果它们不能用PHP编写的话,则不禁止使用其他语言,但必须事先在开发人员的邮件列表中讨论。

外部模板框架?

有大量的框架可以提供HTML页面的模板语言(smarty,twig,...)。我们所知道的最好和更快的只有被称为“PHP”的,并且由于Dolibarr已经依赖于PHP,因此没有必要引入对第三种语言的依赖来制作模板。

所有模板框架都只是预处理器,BEFORE PHP。它不可能比单用PHP更快。当我们说“速度提高是因为模板框架使用了缓存”时,你必须了解一个事实“框架花费的超时时间是由缓存减少的,而不是处理 PHP 文件的速度”。我们应该说“任何模板框架在 PHP 上都有重载,模板框架的缓存只是减少了使用这个框架的减慢效果”,而不是“速度提高了,与纯 PHP 相比总是降低的”。

最重要的是,通过做一个“.tpl.php”文件,完全可以拥有一个模板系统,该文件不包含任何逻辑代码,而只包含HTML和"echo"。我们得到相同的结果(但更快、更容易开发,因为它不需要在调用每个模板之前完成所有的 setXXX)。所有变量(由包含模板的代码所知的)都会自动被 PHP 模板页面所知。没有忘记集合的风险,减少了大量代码行,也节省了大量时间和开发错误。

此外,在以下一种情况下,保证代码和 HTML 输出之间 100% 的隔离是有趣的:当团队构建设计与团队构建逻辑代码完全不同时,如果您构建不太复杂的页面,并且不需要太多的 Ajax 功能(这需要知道代码是如何工作的)。

而且这种情况肯定不是Dolibarr的未来(团队经常会是一样的,Ajax会越来越多,事件,如果我希望不是太多,屏幕越来越依赖于动态或上下文事件,很难用一个简单的模板来做到这一点,而不将模板转换为具有高级代码的页面)。还要设计现在在 CSS 端而不是 PHP 端进行更多管理。

过去,Dolibarr已经尝试过像smarty这样的模板系统。如果在纸上这个想法是好的,我们很快意识到发现错误变得令人头疼,编码成为寻宝,维护和开发做得太慢(与以前输出模板进入每个 PHP 文件的“视图”部分的情况相比),我们忘记了这个想法。实用主义造就了规则。

不使用外部模板系统还有很多其他原因,使用它们的所有参数也是使用 HTML/PHP 作为我们的模板系统的最佳参数。我们只是要求在控制器(PHP 页面的第一部分就在 /* Action */ 行之后)和视图(/* View */ 注释之后的第二部分)之间保持分离。

规范 Dolibarr 和代码框架

代码框架

为了使代码标准化并加快Dolibarr中新组件的开发,可以在 htdocs/modulebuilder/templates 目录中找到准备好的框架文件。

例如:

  • 模块描述示例 myModule.class.php
  • 创建新类的代码示例 skeleton_class.class.php
  • 创建新页面的代码示例 skeleton_page.php
  • 创建要在命令行上运行的脚本的代码示例 skeleton_script.php
  • ...

请注意,PHP代码生成器(ModuleBuilder模块)也使用这些框架,该模块在Dolibarr模块开发章节中有描述,用以提高您的开发速度。

日期和时区

Dolibarr是一个多用户和多地域应用程序。因此,应以正确的格式存储日期。为避免转换出现问题,应遵循以下规则:

  • 内存中的日期必须采用GMT时间戳格式。
  • 存储在数据库中的日期包含来自PHP服务器本地时间请求中提交的日期的时间戳。这不适用于数据库自动更新的日期(数据库中的 tms 字段)。

例如:1970年1月1日巴黎时间的3点钟(TZ=+1)= 1970年1月1日格林威治时间的2点钟(TZ=0),其存储在内存中的值都是7200,并将以字符串“19700101030000”的形式提交给数据库(PHP将其转换为TZ的时间值,而数据库再将其转换回PHP服务器时区的时间)。

因此,所有 select 方法都必须通过调用 db->jdate 函数来转换日期字段,将其从数据库读取的TZ格式日期字段('19700101030000')转换成GMT时间戳格式,以便以GMT时间戳格式检索内存中的信息。而在生成SQL语句时,所有 insert 方法都必须通过调用 db->idate 函数将已知内存格式日期转换为字符串格式日期(参见框架生成的示例)。

  • 数据库自动更新的日期字段(数据库中的 tms 字段)存放根据数据库时区进行转换的时间戳。select 方法通过调用 db->jdate 以GMT时间戳格式检索内存中的数据。如果数据库时区与PHP时区不同(其中一个设置不正确),则创建日期和修改日期之间可能会存在差异。
  • PHP中的日期操作必须使用 dolibarr 中的日期函数:dol_now()、dol_mktime()、dol_stringtotime()、dol_getdate()、dol_time_plus_duree()。您还可以在文件 date.lib.php 中找到其他现成的可用函数。

UTF8/ISO 编码

Dolibarr 按以下方式存储数据:

  • 在数据库中,数据为 UTF8 或 ISO 格式。这取决于数据库的pagecode(页面代码),本选项是在创建数据库时设置的。无论如何,Dolibarr的数据库访问驱动程序(在 lib/database 中)在读取和插入时处理UTF8的转换。
  • 内存数据存储为 UTF8(PHP 对象的实例)格式。
  • 屏幕上显示的网页采用 UTF8 格式。

浮点数,金额和计算

在PHP中,就像在其他语言(如Java)中一样,非整数数据(float、real、double)对于计算来说是不可靠的。 例如:

print 239.2 - 229.3 - 9.9;

你不会得到零,而是一个非常小的负10的幂。如果你得到零,你可以找到其他不起作用的例子。

浮点数的问题是一般性的,计算实数的结果变量必须由 price2num() 函数系统地清理,根据需要将第二个参数设置为“MU”、“MT”或“MS”(参见函数文档)。

print price2num(239.2 - 229.3 - 9.9, 'MT');

如果计算的数据不是一个价格,那么使用 MU、MT、MS 就没有意义了,您必须使用 round() 函数。

表的创建

不要在使用时创建表,即首次使用模块时。如果您正在创建的模块使用的表不是 Dolibarr 代码中的标准表,请确保遵循 模块开发 教程,该教程解释了如何在模块激活时创建表,而不是在模块使用期间创建表(见上文)。

比较版本

如果您的代码需要根据 Dolibarr 版本的不同而执行不同的操作,您可以使用以下技巧来判断和比较版本:

$version=preg_split('/[\.-]/',DOL_VERSION);
if (versioncompare($version,array(5,0,-4)) >= 0) { //mycode for 5.0 only; }	// For dolibarr 5.0.* (the -4 means we include also alpha, beta, rc and rcX)

但是这个解决方案需要 include 函数

$version=preg_split('/[\.-]/',DOL_VERSION);
if (versioncompare($version,array(5,0,-4)) >= 0) { //mycode for 5.0 only; }	// For dolibarr 5.0.* (the -4 means we include also alpha, beta, rc and rcX)

。另一种替代解决方案是执行以下比较操作:

if ((float) DOL_VERSION >= 5.0) { //mycode for 5.0 only; }	// For dolibarr 5.0.*

日志

使用函数在代码中添加日志:

dol_syslog($yourmessage, LOG_INFO|LOG_DEBUG|LOG_WARNING|LOG_ERR);

工作目录

如果需要在代码中创建工作目录,请将该目录引用为 DOL_DATA_ROOT.'/mymodule'

可以使用以下代码在运行时使用代码创建目录:

$mymoduledir=DOL_DATA_ROOT.'/mymodule';
dol_mkdir($mymoduledir);

如果需要包含临时数据的目录,则该目录应为 DOL_DATA_ROOT.'/mymodule/temp' 。

设计模式与面向对象编程

GoF 设计模式

由四人帮(Gang Of Four)定义的设计模式(参见维基百科上的 设计模式 )。

创建型模式 (GoF)

在Dolibarr中,没有实现特定的创建型设计模式。有一些对象在设计思想上接近 Singletons 和 Fabriques,但在语法上不一致,以便尽可能与不是纯对象语言的PHP兼容。

结构型模式 (GoF)

在Dolibarr中,没有实现特定的结构型设计模式。

行为型模式 (GoF)

在Dolibarr中,没有实现特定的行为型设计模式。

企业设计模式 (Martin Fowler)

组织代码的方法

Martin Fowler 确定了三种组织代码的方法,称为模式:

  • 事务脚本(每个用户操作的源代码都是线性的)

这是过程语言中使用的老式模式。

缺点:代码冗余。需要了解物理模型以开发业务部件。

  • 域模型

这个概念可用于对象语言。业务流程(必须在此之前确定)是对象类的基础。

缺点:模型维护起来非常复杂。

  • 表模块

介于前两种方法之间的中间方法,其中每个数据库表都只有一个类实例。

-> 如代码框架所示(见前面章节),Dolibarr基于 表模块 的原理设计。

业务逻辑 - 数据之间的交互 (ORM)

有三种交互模式:

  • 表和行 Data Gateway

这是最简单的方法。我们为每个表创建一个类,每个类都是到相应表的桥梁,并在该表上提供CRUD方法(Ceate, Read, Update, Delete)。其次,类的实例是表的记录。类仅包含表行或列的访问代码。最后,必须将业务代码添加到其他类中。这些表类和业务类之间的链接关系必须是已知的,或可从开发人员的知识中推断出来的。

示例:此模式由某些ORM框架使用,如 iBatishttp://ibatis.apache.org/)。

  • Active Record

与前一个相同,但允许在类上添加一些业务函数,前提是这些函数非常特定于表或记录所承载的业务概念。这是提供最佳 功能/复杂性 比的模式。

示例:这是Dolibarr和许多其他PHP应用程序开发的首选模式,这些应用程序有自己的框架和开发实践。

  • 数据 Mapper

类表示问题的实体,而不是数据。所以您必须两次,三次 ... 地调用 Mapper 类访问数据。这种模式在理论上更“纯粹”,因为它更接近业务,但也有非常复杂和难以在现实中维护的缺点(观察到的实践经验不符合理论的承诺)。

示例:这是 ORM 框架 Propel 的选择(http://propel.phpdb.org/trac/)。因此,它可以在基于此ORM的大量的应用程序中找到。

-> 对于Dolibarr开发,建议使用 Active Record 链接模式,该模式提供了接近业务模型的优点,而不具有复杂性,也不过度隐藏技术。正是在这种模式下,开发、代码理解以及技术和/或功能维护似乎最有成效(然而,这是纯粹主义者和实用主义者之间的一场永恒的辩论,没有人真正正确,因为一切都取决于要实现的目标)。

图形界面设计模式(MVC)

用户界面有多种设计模式(MVC、MVP、MVVM)。许多人将此设计模式与使用模板框架的设计模式相混淆。这无关紧要。模板框架(见前面章节)仅与这些设计模式的“V”部分有关。

-> Dolibarr使用MVC设计模式。M部分由包含用于访问表的CRUD(创建-读取-更新-删除)方法的DAO类实现。

许多框架选择将C(控制器)代码部分和V(视图)代码部分放在单独的文件甚至目录中。从理论上看,似乎更容易维护,但实际上,我们必须不断地在n个文件之间切换,因C与V紧密相连。同样,在Dolibarr中,隔离是通过一个简单的注释块完成的。因此,C(控制器)代码位于下述标记处:

/*
 * Actions
 * /

它后面是V (Vue)(视图)代码,通过如下标记与C代码隔离开:

/*
 * View
 * /

这种隔离模式对新增需求的开发产生了中性影响,但大大减少了维护和修复错误所需的时间,因此不打算切换到另一种类型的隔离(确实,现实生活中的观察结果并不总是学术理论所期望的)。重要的是,C和V之间总是存在隔离,无论这种隔离是如何实现的,只要它存在(提醒:不要将C和V的隔离概念与为视图提供数据的代码和格式化视图的代码的分离概念混淆,这是视图设计模式的分离原则,请参阅下一节)。

视图 - 展现的设计模式

有专门用于视图设计模式的框架,这部分涉及格式化用户看到的屏幕。

旧版本的Dolibarr使用Smarty。这个框架是一个已经非常强大的屏幕格式化框架的覆盖层,名为...PHP。由于优点远小于缺点,它已被放弃,取而代之的是PHP模板(.tpl.php文件),PHP模板仍然是最简单、最通用、最可扩展和最高效的展现框架。

它唯一的缺陷是,它不能100%保证开发人员不会在视图中集成与展现无关的逻辑(除了用于生成、格式化、HTML输出、判断逻辑或循环的其它代码)。然而,这一点应该由项目经理代码验证机制控制。

另一方面,正是经验和“在现场”观察到的更高生产率,促使人们放弃了外部展现框架,转而使用PHP框架。