语言和开发规则
这是我们在Dolibarr项目中使用的语言、语法和规范的一些规则:
版本
- Dolibarr 工作于:
- 所有操作系统 OS (Windows, Linux, MACOS...)
- PHP 5.6.0+ (requires functions like DateTimeZone.getOffset, php-intl) (Must work with no need of complementary PHP module, except module to PHP module to access database).
- Mysql 5.1+
版权规范
- 所有 PHP 文件必须以下面文本开头。
<?php
/* Copyright (C) YYYY John Doe <email@email.com>
*
* Licence information
*/
...
当编辑项目的现有文件时,必须在他人下添加版权行。
PHP 规范
PHP
- Dolibarr 以 PHP语言编写,支持所有高于 5.6.0+ (requires functions like DateTimeZone.getOffset, php-intl)的 PHP 版本。 所有文件扩展名为.php
- PHP 超级全局变量必须使用专用的操作符如: $_COOKIES, $_SERVER, $_ENV,但是为取得$_GET或$_POST的值,应使用 Dolibarr 函数GETPOST()
其他操作符 ($HTTP_SERVER_GET, ...) 不建议使用。
- 代码应该不依赖于 register_long_arrays 的设置。
- 此外,当PHP选项register_globals关闭(PHP建议)时,代码必须工作。如果register_globals打开(大多数安装操作的默认情况),它也必须工作。
- 不要使用 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"加载作为模板样式的php文件,其中包含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作为规则文件来控制PHP的编码风格。
- 注 3: 您可能注意到当前代码还不符合PSR-2。原因是我们必须“现在”遵循PSR-2的“必须”规则,但不是过去。因此,如果发现这种情况,请随意更改编码风格以匹配新规则(请记住2个例外)。
- 注 1: 下面的规则是非常重要的:
类和属性结构
在不同的类中有一些相同的属性。为了避免使用不同的名称,我们将使用以下属性名称:
- 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: 修改日期 - date_validation: 确认日期 - tms - fk_user_creation: 创建者的ID - fk_user_modification: 修改者的ID - import_key qui contiendra le code d'import YYYYMMDDHHMMSS si vous faites des imports en masse dans la table. - fk_user_validation: 确认者的ID
SQL 规则
DDL 文件格式
包含数据库结构定义(DDL文件)的文件必须每张表有两个:
- 第一个文件定义表及其字段。文件名包含表名,例如:llx_mytable.sql
将为每个字段添加注释以解释其用法。
- 第二个文件定义了所有外键、性能索引或其他约束,文件名如下:llx_mytable.key.sql。
这些文件必须存储在相应目录中,所有标准文件的目录install/mysql/tables中,或者外部模块提供的表的目录mymodule/tables 中。
例如: 创建表table 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;
例如: 为表table 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);
表和字段结构
- 表结构
当您创建一个新表时,建议使用与其他Dolibarr表相同的约定。这意味着以下字段:
- rowid INTEGER AUTO_INCREMENT PRIMARY KEY 技术ID - entity INTEGER default 1 多公司ID - date_creation datetime 创建日期 - tms timestamp 这将包含最后修改的日期(数据库自动管理该字段,不需要通过代码管理它,只需创建该字段) - import_key 如果您进行批量导入,则将包含导入代码YYYYMMDDHHMMSS。
最终:
- fk_user_creat integer 创建者ID - fk_user_modif integer 修改者ID - fk_user_valid integer 确认者ID - fk_soc integer 这是合伙人的ID(如果适用)
备注字段:
- note_private 对象的私人备注 - note_pubic 对象的公开备注
或者
- note 备注
- 字段类型:
为了与任何国家的金额要求的任何准确性兼容、与任何数据库语法兼容以及与Dolibarr升级框架兼容,我们在数据库中使用以下的字段类型:
- integer 外键 - double(24,8) 金额 - double(6,3) 税率 - real 数量 - varchar 字符串 (另外,如果长度是1,char类型就越来越没用) - timestamp 自动更新的日期+时间的字段 - datetime 日期+时间的字段 - date 日期 - text or medium text 大字段 (不许索引)
所有的业务规则必须位于相同的位置,进入PHP代码,不进入客户端,也不进入数据库,这就是为什么类型enum也不被允许。 由于兼容性原因,其他类型是不允许的。
- 所有表都有一个前缀,以避免与其他项目的名称冲突。在当前版本中,这个前缀可以在安装Dolibarr时修改。其默认值为llx_。
主键
表的主键必须为 rowid.
一些旧表不使用此规则,并使用名为id(如Table llx_c_actioncomm)的主键,但原因在于历史,并且不应该再发生这种情况。
外键
外键的名称必须以前缀fk_开头,后跟表名(即引用表的名称)(这是为了避免项目中的重复名称,即使某些DBMS如Postgresql在不同的表中也不允许),然后是字段名称(即参考字段的名称)(这是为了允许在同一表中有几个外键)。
例如: fk_facture_fourn_fk_soc 是表llx_facture_fourn中的外键,用于此表中的字段fk_soc(引用另一个表中的rowid字段)
注意:如果您开发了自己的外部模块,则它必须没有指向Dolibarr标准表的外键。这将破坏标准Dolibarr升级,修复,备份和恢复工具,也可能破坏标准的功能。
替代关键字
Sometimes, we need another unique keys than primary key. We can add in this case an alternate unique key. When we need this, we can create an alternate unique key. Such an index is called by a name that start by prefix uk_ followed by an underscore, then the the table name (this is required to avoid duplicate names of unique keys that may create problems for some DBMS like Postgresql) and then another string to define the key (this is to allow to have several unique keys on same table).
例如: uk_societe_code_client 是表llx_societe在code_client字段上的一个唯一性的关键字
索引性能
Some fields are often used as search or order criteria, or for joins. In such case, we need to set a performance index on field to increase performances. Such indexes are named with a prefix idx_ then the table name and then the field name.
Example: idx_societe_user_creat is a performance index on table llx_societe for field user_creat
SQL编码规则
- Alias usage/Fields naming
When doing select, we can use alias to simplify writing/reading of requests:
select chp1, chpxxx2 as chp2 from table2 as t1, table2 as t2 where t1.chpx = t2.chpy
However, we must not used alias for update request as they are not compatible with Mysql 3.1.
- Using SELECT * is forbidden ! When using SELECT you must define complete list of fields to get. This avoids confusion. Example:
SELECT field_a, field_b, field_c FROM table_1 WHERE field_d = '$id'
- Into SQL requests, you must quote fields except the fields that contain amounts which must be stored as double or real type. Quotes on numbers may result in saving as a different value. For example 412.62 in an insert will be saved as value 412.61999512 into database (due to implicit conversion string to numeric) if the target field has type double(24,8). Only PHP see value 412.61999512. Other tools will see 412.62 giving a sense that there is no problem. But it's PHP that has the good vision. There is really a wrong value into database. By removing quotes on numbers, no problem occurs.
Example:
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')
Note, problem of float numbers is same problem on all langauges and not only when inserting data into database. It occurs also with any language when you work on "real" numbers, so numbers must be, as soon as they are affected, cleaned with function price2num with second parameter defined to : 'MU' (for unit prices), 'MT' (for total prices) or 'MS' (otherwise) depending on usage of number. (see function documentation)
- Functions NOW, SYSDATE or DATEDIFF are forbidden inside SQL requests. If you must use the current date into a field, value must come from the PHP and not from the database engine. This is for better portability of code and correct management of TimeZone.
For example, don't do:
$sql="SELECT rowid FROM table where datefield = NOW()";
but do:
$sql="SELECT rowid FROM table where datefield = '".$this->db->idate(dol_now())."'";
例如,不要这样做:
$sql="SELECT rowid FROM table where DATEDIFF(table.datefield, NOW()) > 7";
but do:
$sql="SELECT rowid FROM table where datefield < '".$this->db->idate(dol_now() - (7 * 24 * 3600))."'";
An other advantage of this rule, is that request benefits of index because we are making a compare of a field with a fixed value. When using datediff, you make an operation on field before comparison, this means database can't use the index on field, resulting on very bad performance compared to solution without the datediff.
Mysql 特点
- Tables must be declared with format InnoDB.
This format supports foreign keys and their restrictions, and transactions integrity is also supported. This guarantees that Dolibarr events keep all data with correct values between tables even when transaction modify different tables.
- Dolibarr MUST work even if Mysql option strict is active.
To activate it (required when developing on Dolibarr), add the following line into the config file of your Mysql server (my.cnf or my.ini)
sql-mode="STRICT_ALL_TABLES,ONLY_FULL_GROUP_BY,NO_ZERO_DATE,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
PostgreSQL specificities
Only Mysql SQL files must be maintained. Those files are converted "on the fly" by the database Dolibarr driver.
There is an exception The SQL "UPDATE FROM":
MySQL Syntax:
UPDATE table_taget as target, table_source as source SET fieldtarget=source.fieldsource
WHERE source.rowid=target.rowid;
PgSQL Syntax:
UPDATE table_taget as target SET fieldtarget=source.fieldsource
FROM table_source as source WHERE source.rowid=target.rowid;
There is no native SQL request "update from" in dolibarr core. But in your module you should do :
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 used must be HTML compliant and not XHTML. All attributes of HTML tags must be in lower case and quoted with ".
- Links href must be absolute and use the function dol_buildpath() to get absolute path from a relative path and img tag must be build using function img_picto().
For example:
print '<a href="'.dol_buildpath('/mydir/mypage.php').'">'.img_picto('Texte alt','namepictopng','').'</a>';
- HTML tables must have columns with no forced width, except for columns that contains data we know the length. For example, a column with a picto only can be forced to with="20px".
Otherwise, we must avoid forcing the column width. Reason is that, in most cases, the browser make a better works to define column width automatically than forced values, and it works whatever is the resolution.
- Javascript/ajax code and call to javascript files into php pages must be avoided. However, if you need to include javascript code, you must add a condition on "$conf->use_javascript_ajax"
if ($conf->use_javascript_ajax) {
... // php code generating javascript here
}
- Popup windows must not be used, except for tooltips (and must have a condition as explained before).
- External scripts must be written in Perl if they can't be written in PHP. Usage of another language is not forbidden but must be argued before in the development mailing-list.
外部模板框架?
There is ton of frameworks to provide a templating language of an HTML page. The best and faster is just called "PHP", and because Dolibarr already depends on PHP, there is no need to introduce a dependency on a third language.
All templating frameworks are just preprocessor, BEFORE PHP. It can't be faster than PHP alone. When we say "speed is increased because templating framework use a cache", you must read in fact "the overtime spent by the framework is reduced by cache, not the speed of processing PHP file". We should say "any templating framework has an overload over PHP, the cache of framework, just reduce the slowing effect of having this framework", but not "the speed is increased, it is always decreased compared to pure PHP".
Above all, having a templating system is completely possible by doing a ".tpl.php" file that dos not contains any logic code, but only HTML and "echo". We get same result (but faster and easier to develop because it does not need to do all the setXXX before calling each template). All variables (known by code that include the template) are automaticaly known into a PHP template page. No risk to forget a set, and a lot of line of codes reduced, a lot of time and development errors are also saved.
Also, keeping a 100% isolation between code and HTML output is interesting in only 1 situation: When teams building design is complety different than team building logic code, AND if you build not too sophisticated pages, AND with no need of too many Ajax features (this need to know how code works). And this situation is surely not the Dolibarr future (team will often be same, Ajax will be more and more present, event if I hope not too much, and screens are more and more dependent of dynamic or contextual events, difficult to have this with onesimple template without transforming the template into a page with high level of code).
There is a ton of other reasons to not use an external templating system and all arguments to use them are also the best argument to use PHP as our templating system.
Dolibarr 规范和代码框架
框架代码
To standardize the code, and to speed up the development of new components in Dolibarr, you'll find 4 skeletons fully prepared in the directory dev/skeletons.
- 1 that serves as an example of the module description: myModule.class.php
- 1 that serves as an example of code for creating a new class: skeleton_class.class.php
- 1 that serves as an example of code for creating a new page: skeleton_page.php
- 1 that serves as an example of code for creating a script for executing command lines: skeleton_script.php
Use it as an example. Note that the skeletons are also used by the PHP code generator, which is described in the development chapter of Dolibarr modules, to speed up your development.
日期和时区
Dolibarr is an application that is multi-user and multi-location. It's therefore necessary to store dates in the right format. To avoid problems with conversions, the following rules should be applied:
- A date into memory must always be stored with Timestamp GMT format.
- A date stored into database, is the GMT Timestamp of date submitted into the request usng the PHP server timezone. This does not apply to date update automatically by database (fields tms into database).
For exemple le 1st january 1970, 3 hour at Paris (TZ=+1) = 2 hour at Greenwitch (TZ=0) will be stored into memory with value 7200 and will be submitted into a SQL request to database with the string '19700101030000' (PHP convert into its TZ hour and database will unconvert it using its timezone too that is same than PHP).
All select methods should translate date fields, that are with format TZ of database ('19700101030000'), into a timestamp field by calling the method db->jdate. This is to store into memory a GTM Timestamp date. All insert methods must convert, during generation of SQL request, the memory date into the string by using db->idate (you may find examples into skeleton).
- Dates that are updated automatically (field tms into database) contains a GMT Timestamp GMT of date when change is done. The select will also use the db->jdate (that use PHP server TZ) to convert read data into a GMT Timestamp into memory. So if timezone of database differs from timezone of PHP server (one of them is not correctly set), you may experience differences between creation date and update date.
- Manipulation date with PHP must be done using the Dolibarr date functions: dol_now(), dol_mktime(), dol_stringtotime(), dol_getdate(), dol_time_plus_duree(). You may also find other functions available into file date.lib.php.
UTF8/ISO 编码
Dolibarr stores data in the following way:
- In database, data is stored in UTF8 or ISO. It depends on the database's pagecode therefore these options are set at creation time. In any case, Dolibarr's database driver (in lib/database) deals with it to convert from/to UTF8 at insertion and readout.
- Memory data is stored in UTF8 (PHP object instances).
- Screen displayed web pages are UTF8 (for versions prior to 2.5.1, the deprecated $character_set parameter from conf.php file defines output format).
浮点数,金额和计算
With PHP, like other languages (Java for exemple), non integer data (float, real, double) are not reliable for calculation. Try to make for example
print 239.2 - 229.3 - 9.9;
You wont get zero but a very small decimal number. If you get zero her, you should be able to find other examples that don't work. Problem of float is general, so a variable that is a result of a calculation using decimal numbers must ALWAYS be cleaned using the function price2num() with the econd parameter to: 'MU', 'MT' or 'MS' depending on need (see description of function).
print price2num(239.2 - 229.3 - 9.9, 'MT');
If data manipulated is not an amount, then using MU, MT, MS has no sense, and you must use the function round().
表的创建
Do not create tables, on the fly, during execution by a standard user, we mean, during current usage of software. If you create a module that uses its own table, not available in the default Dolibarr code, then take a look at tutorial Module development. It explains how to provide new tables with your module, so that, the tables will be created during module activation and not during module usage.
比较版本
If your code need to make different things depending on Dolibarr version, you can use the following tip to detect and compare versions
$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)
But this solution need to include the function versioncompare. An alternative solution to test version is to do:
if ((float) DOL_VERSION >= 5.0) { //mycode for 5.0 only; } // For dolibarr 5.0.*
日志
Add logs to your code using function
dol_syslog($yourmessage, LOG_INFO|LOG_DEBUG|LOG_WARNING|LOG_ERR);
工作目录
If you need to create a working directory, into your code, refer to it with DOL_DATA_ROOT.'/mymodule'
The directory can be created into your code by the following function:
$mymoduledir=DOL_DATA_ROOT.'/mymodule';
dol_mkdir($mymoduledir);
If you need a directory to store temporary data, this directory must be DOL_DATA_ROOT.'/mymodule/temp'
设计模式与对象程序设计
创建设计框 (GoF)
Design patterns defined by the Gang Of Four (see wikipédia on Design patters). No usage of such patterns is required. We found some objects next to Singletons or Factory but not completely compliant with syntax, this is to be compatible with PHP 4 that is not a pure object language.
结构设计模式 (GoF)
Design patterns defined by the Gang Of Four (see wikipédia on Design patters). No usage of such patterns is required.
行为设计模式 (GoF)
Design patterns defined by the Gang Of Four (see wikipédia on Design patters). No usage of such patterns is required.
企业设计模式 (Martin Fowler)
代码组织模式
Martin Fowler has identified 3 ways to organize code:
- The Transaction Script (The source code is linear for each user action).
This is the old school used by all procedural languages. Inconvenient: Redundancy of code. Need to know the physical model of data to develop.
- The Domain Model
This notion is available with object languages. It is business process (to identify before) that are used for objects classes. Inconvenient: Model very complex to maintain.
- The Table Module
This is a mix between 2 previous where we have only one unique class for each table of database.
-> As shown in code skeletons (see previous chapter), Dolibarr uses concept of Table Module.
业务逻辑之间的通信 - data (ORM)
There are 3 ways to make links:
- The Table And Row Data Gateway
This is the most simple. You have one class per table and each class is a link to the table with CRUD methods (Ceate, Read, Update, Delete). A class instance is a record in the table. The class contains only code to reach lines and fields of table.
Example: This mode is used by some ORM Frameworks, like iBatis.
- The Active Record
Same as previous, but we are allowed to add some business functions into the class, if such functions are dedicated to the table or recording into this table.
Example: This mode is used for Dolibarr development and most PHP softwares that include their own framework and best practices.
- The Data Mapper
Classes represent entities of the problem and not the data. So you have to double, triple ... these classes with Mapper classes to access the data. More "purist" on paper (closer of business), this method also has the disadvantage of being more complex in practice.
Example: This is the choice if you use the ORM Framework Propel. We find this model in heavier applications, based on this ORM among others.
-> For Dolibarr development, it is recommended to use the connection mode Active Record, which offers the advantages of a model close to the business without having the complexity, without obfuscating technical architecture. It is by this way that the development, understanding of code and technical maintenance and / or business behaviour seems the more productive (this is however an ongoing debate between the purists and the pragmatists, debate in which nobody can really be right, because it depends on the objectives).