Changes

m
Line 2: Line 2:  
<!-- You can edit this section but do NOT remove these comments
 
<!-- You can edit this section but do NOT remove these comments
 
     Links below will be automatically replicated on translated pages by PolyglotBot -->
 
     Links below will be automatically replicated on translated pages by PolyglotBot -->
 +
[[de:Sprachen_und_Programmierregeln]]
 
[[fr:Langages_et_normes]]
 
[[fr:Langages_et_normes]]
 
[[es:Lenguaje_y_Normas_de_Desarrollo]]
 
[[es:Lenguaje_y_Normas_de_Desarrollo]]
Line 13: Line 14:  
=Versions=
 
=Versions=
   −
*Dolibarr must work on:
+
*Current version of Dolibarr must work on:
    
#All OS (Windows, Linux, MACOS...)
 
#All OS (Windows, Linux, MACOS...)
#PHP {{PHPMinVersion}} (Must work with no need of complementary PHP module, except module to PHP module to access database).
+
#PHP {{PHPMinVersion}} (Must work with no need of complementary PHP module, except module to PHP module to access database). See [[List of releases, change log and compatibilities]] for pre-requisites for older versions.
 
#MySQL {{MySqlMinVersion}}
 
#MySQL {{MySqlMinVersion}}
   Line 23: Line 24:  
*All PHP files must start with a header that looks like
 
*All PHP files must start with a header that looks like
   −
<source lang="php">
+
<syntaxhighlight lang="php">
 
<?php
 
<?php
 
/* Copyright (C) YYYY John Doe  <email@email.com>
 
/* Copyright (C) YYYY John Doe  <email@email.com>
Line 30: Line 31:  
  */
 
  */
 
...
 
...
</source>
+
</syntaxhighlight>
 
When you edit an existing file of project, you must add a Copyright line under others.
 
When you edit an existing file of project, you must add a Copyright line under others.
   Line 38: Line 39:  
*Dolibarr is written with PHP and supports all versions of PHP higher than {{PHPMinVersion}}. All files must end with extension .php
 
*Dolibarr is written with PHP and supports all versions of PHP higher than {{PHPMinVersion}}. All files must end with extension .php
   −
*Usage of PHP superglobals variables must use the dedicated operators $_COOKIES, $_SERVER, $_ENV but use the Dolibarr function GETPOST() to get the contents of $_GET or $_POST..
+
*Usage of PHP superglobals variables must use the dedicated operators $_COOKIE, $_SERVER, $_ENV but use the Dolibarr functions [[Fonctions utiles Dolibarr|GETPOST...()]] to get the contents of $_GET or $_POST..
    
*Other operators ($HTTP_SERVER_GET, ...) are now deprecated in PHP, so they must not be used. Code must work if option '''register_long_arrays''' is set to off. Moreover, the code must work when PHP option '''register_globals''' is off (recommended by PHP). It must also work  if the option '''register_globals''' is on (by default on a lot of installations).
 
*Other operators ($HTTP_SERVER_GET, ...) are now deprecated in PHP, so they must not be used. Code must work if option '''register_long_arrays''' is set to off. Moreover, the code must work when PHP option '''register_globals''' is off (recommended by PHP). It must also work  if the option '''register_globals''' is on (by default on a lot of installations).
Line 46: Line 47:  
*When several variables must be initialized with the same value, you must use individual declarations (separated by ;)
 
*When several variables must be initialized with the same value, you must use individual declarations (separated by ;)
   −
<source lang="php">
+
<syntaxhighlight lang="php">
$var1=1;
+
$var1 = 1;
$var2=1;
+
$var2 = 1;
$var3=1;
+
$var3 = 1;
</source>
+
</syntaxhighlight>
 
instead of
 
instead of
<source lang="php">
+
<syntaxhighlight lang="php">
$var1=$var2=$var3=1;
+
$var1 = $var2 = $var3 = 1;
</source>
+
</syntaxhighlight>
 
which is slower.
 
which is slower.
   Line 61: Line 62:  
====Strings====
 
====Strings====
   −
*Strings must be delimited by a single or double quote and a variable within the string must be outside of the quote.
+
*Strings must be have variables outside of the quote.
   −
<source lang="php">
+
<syntaxhighlight lang="php">
print 'My text show my '.$variable.' !';
+
print "My text show my ".$variable." !\n";
</source>
+
</syntaxhighlight>
    
<br />
 
<br />
Line 73: Line 74:  
*Comments must use the C syntax, ie a double slash for a comment on one line and a slash-star to open a bloc for several lines
 
*Comments must use the C syntax, ie a double slash for a comment on one line and a slash-star to open a bloc for several lines
   −
<source lang="php">
+
<syntaxhighlight lang="php">
 
/* Bloc of comment
 
/* Bloc of comment
 
  *
 
  *
Line 91: Line 92:  
     // code
 
     // code
 
}
 
}
</source>
+
</syntaxhighlight>
   −
<br />
+
<br>
    
*Functions must return a value equal or higher than 0 if successful and strictly lower than 0 if error.
 
*Functions must return a value equal or higher than 0 if successful and strictly lower than 0 if error.
Line 118: Line 119:  
{{TemplatePHPFields}}
 
{{TemplatePHPFields}}
   −
=SQL rules=
+
=SQL and Database rules=
    
==DDL file format==
 
==DDL file format==
Line 133: Line 134:  
Example: '''file for creating the table llx_mytable will be llx_mytable.sql''':
 
Example: '''file for creating the table llx_mytable will be llx_mytable.sql''':
   −
<source lang="sql">
+
<syntaxhighlight lang="sql">
 
-- ===========================================================================
 
-- ===========================================================================
 
-- Copyright (C) 2013 Author <email@author.com>
 
-- Copyright (C) 2013 Author <email@author.com>
Line 167: Line 168:  
   import_key varchar(14)                -- Use by import process
 
   import_key varchar(14)                -- Use by import process
 
)type=innodb;
 
)type=innodb;
</source>
+
</syntaxhighlight>
    
Example: '''file for creating keys/indexes for the table llx_mytable will be llx_mytable.key.sql''':
 
Example: '''file for creating keys/indexes for the table llx_mytable will be llx_mytable.key.sql''':
   −
<source lang="sql">
+
<syntaxhighlight lang="sql">
 
-- ===========================================================================
 
-- ===========================================================================
 
-- Copyright (C) 2013 Author <email@author.com>
 
-- Copyright (C) 2013 Author <email@author.com>
Line 192: Line 193:     
ALTER TABLE llx_mytable ADD CONSTRAINT fk_mytable_fk_field FOREIGN KEY (fk_field) REFERENCES llx_matablepere (rowid);
 
ALTER TABLE llx_mytable ADD CONSTRAINT fk_mytable_fk_field FOREIGN KEY (fk_field) REFERENCES llx_matablepere (rowid);
</source>
+
</syntaxhighlight>
    
==Table and fields structures==
 
==Table and fields structures==
Line 204: Line 205:  
  - tms            timestamp                  that will contain date of last modification (the database manages this field automatically, no need to manage it by the code, just create the field)
 
  - tms            timestamp                  that will contain date of last modification (the database manages this field automatically, no need to manage it by the code, just create the field)
 
  - date_valid      datetime                    that is the validation date (if applicable)
 
  - date_valid      datetime                    that is the validation date (if applicable)
  - import_key      varchar(32)                that will contains the import code YYYYMMDDHHMMSS if you make mass import.
+
  - import_key      varchar(32)                that will contain the import code YYYYMMDDHHMMSS if you make mass import
 
  - status          smallint                    to store a status
 
  - status          smallint                    to store a status
Eventually
+
Optional
  - fk_user_creat integer that is the id of user making creation
+
  - fk_user_creat or fk_user_author integer that is the id of user making creation
 
  - fk_user_modif integer that is the id of user making change
 
  - fk_user_modif integer that is the id of user making change
 
  - fk_user_valid integer that is the id of user validating record (if applicable)
 
  - fk_user_valid integer that is the id of user validating record (if applicable)
Line 214: Line 215:  
Note field:
 
Note field:
 
  - note_private text for private comment of the object
 
  - note_private text for private comment of the object
  - note_pubic text for public comment of the object
+
  - note_public text for public comment of the object
 
or  
 
or  
 
  - note text if there is no need to have private and public
 
  - note text if there is no need to have private and public
Line 221: Line 222:  
*Type of fields:
 
*Type of fields:
   −
Well, to be compatible with any accuracy required by any country on amounts, to be compatible with any database syntax and with the Dolibarr upgrade framework, we will use the following types for database fields:
+
To be compatible with any accuracy required by any country on amounts, to be compatible with any database syntax and with the Dolibarr upgrade framework, we will use the following types for database fields:
    
  - integer for a primary key, an integer or any of foreign key (bigint may also be accepted for very large tables)
 
  - integer for a primary key, an integer or any of foreign key (bigint may also be accepted for very large tables)
Line 234: Line 235:  
  - text or medium text for large fields (no index allowed on such fields)
 
  - text or medium text for large fields (no index allowed on such fields)
   −
All business rules must be on same place, into PHP code, not into client, neither into database, that's why type enum is also not allowed.
+
All business rules must be in the same place, in PHP code, not in the client or the database, that's why type enum is also not allowed.
Other type are not allowed for compatibility reasons.  
+
Other types are not allowed for compatibility reasons.  
   −
*All tables has a prefix to avoid name conflicts with other projects. In current version this prefix is fixed ans can't be modified. Its value is <tt>llx_</tt>. In a future version this value should be modified during installation process.
+
*All tables have a prefix to avoid name conflicts with other projects. In the current version this prefix is fixed and can't be modified. Its value is <tt>llx_</tt>. In a future version this value should be modified during installation process.
    
==Primary keys==
 
==Primary keys==
 
The primary key of a table must be called <tt>rowid</tt>.
 
The primary key of a table must be called <tt>rowid</tt>.
   −
Some old tables does not use this rule and use a primary key called <tt>id</tt> (ie [[Table llx_c_actioncomm]]), but the reason is history and this should not happen anymore.
+
Some old tables do not use this rule and use a primary key called <tt>id</tt> (e.g. [[Table llx_c_actioncomm]]). The reason is historical and this should not happen anymore.
    
==Foreign keys==
 
==Foreign keys==
Line 250: Line 251:  
''fk_facture_fourn_fk_soc''  is a foreign key in the table llx_facture_fourn for the field fk_soc in this table (that references the rowid field in another table)
 
''fk_facture_fourn_fk_soc''  is a foreign key in the table llx_facture_fourn for the field fk_soc in this table (that references the rowid field in another table)
   −
Note: If you develop your own external module, it must have no foreign keys that point to Dolibarr standard tables. This will break standard dolibarr upgrades, repair, backup and restore tools and may also break standard features.
+
Note: If you develop your own external module, it must have no hard foreign keys that point to Dolibarr standard tables. This will break standard dolibarr upgrades, repair, backup and restore tools and may also break standard features. Foreign keys must be soft foreign keys so managed by the code with no database contraints.
    
==Alternative keys==
 
==Alternative keys==
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 <tt>uk_</tt> 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).
+
Sometimes, we need other unique keys than primary key. When we need this, we can create an alternate unique key. Such a key should have a name that starts with <tt>uk_</tt>, then 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 having several unique keys on the same table).
    
Example:
 
Example:
Line 269: Line 270:     
When doing select, we can use alias to simplify writing/reading of requests:
 
When doing select, we can use alias to simplify writing/reading of requests:
<source lang="sql">
+
<syntaxhighlight lang="sql">
 
select chp1, chpxxx2 as chp2 from table2 as t1, table2 as t2 where t1.chpx = t2.chpy
 
select chp1, chpxxx2 as chp2 from table2 as t1, table2 as t2 where t1.chpx = t2.chpy
</source>
+
</syntaxhighlight>
 
However, we must not used alias for update request as they are not compatible with Mysql 3.1.
 
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. And above all, this make reengeering of code easier and make impact analysis of change on a field possible. Example:
 
*Using SELECT * is forbidden ! When using SELECT you must define complete list of fields to get. This avoids confusion. And above all, this make reengeering of code easier and make impact analysis of change on a field possible. Example:
   −
<source lang="sql">
+
<syntaxhighlight lang="sql">
 
SELECT field_a, field_b, field_c FROM table_1 WHERE field_d = '$id'
 
SELECT field_a, field_b, field_c FROM table_1 WHERE field_d = '$id'
</source>
+
</syntaxhighlight>
   −
*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.
+
*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 letting think 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:
 
Example:
<source lang="sql">
+
<syntaxhighlight lang="sql">
 
Good:    INSERT INTO table_1 (field_txt, field_num) VALUES ('txt', 412.62)
 
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')
 
Bad:      INSERT INTO table_1 (field_txt, field_num) VALUES ('txt', '412.62')
</source>
+
</syntaxhighlight>
   −
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 :
+
Note, the problem of float numbers is the same on all languages 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 into variable, 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)
 
'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.
+
* SQL Date functions NOW, SYSDATE, DATEDIFF or DATE are forbidden inside SQL requests. Other functions like (MONTH, YEAR) must also be avoided if possible. 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 3 reasons:
 +
** better portability of code
 +
** better management of TimeZone (reference timezone is the PHP timezone and timezone of database may differs so all dates functions must be on PHP side).
 +
** better performance (see example and final comment)
    
For example, don't do:
 
For example, don't do:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$sql="SELECT rowid FROM table where datefield = NOW()";
 
$sql="SELECT rowid FROM table where datefield = NOW()";
</source>
+
</syntaxhighlight>
 
but do:
 
but do:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$sql="SELECT rowid FROM table where datefield = '".$this->db->idate(dol_now())."'";
 
$sql="SELECT rowid FROM table where datefield = '".$this->db->idate(dol_now())."'";
</source>
+
</syntaxhighlight>
    
For example, don't do:
 
For example, don't do:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$sql="SELECT rowid FROM table where DATEDIFF(table.datefield, NOW()) > 7";
 
$sql="SELECT rowid FROM table where DATEDIFF(table.datefield, NOW()) > 7";
</source>
+
</syntaxhighlight>
 
but do:
 
but do:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$sql="SELECT rowid FROM table where datefield < '".$this->db->idate(dol_now() - (7 * 24 * 3600))."'";
 
$sql="SELECT rowid FROM table where datefield < '".$this->db->idate(dol_now() - (7 * 24 * 3600))."'";
</source>
+
</syntaxhighlight>
    
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.
 
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.
   −
*Use $db->ifsql for SQL IF
+
* The statement WITH ROLLUP is prohibited:
 +
 
 +
This statement should not be used: it is not handled the same way by different databases.
 +
Also, using WITH ROLLUP breaks the purity of the returned data. The intermediate subtotal generation aggregation performed by this statement can easily be done using PHP and helps keep the returned data array clean (not corrupted by artificially inserted data).
 +
 
 +
* The sql function GROUP_CONCAT can't be used. It is not portable and ask the database to make subrequests for line into main table, this can be done easier by doing a subrequest inside the PHP loop when building the output of each line.
 +
 
 +
* Use $db->ifsql for SQL IF
    
Do not include the IF into your forged SQL request. But use instead $db->ifsql() method to you will make a SQL IF that is compatible with all SQL databases.
 
Do not include the IF into your forged SQL request. But use instead $db->ifsql() method to you will make a SQL IF that is compatible with all SQL databases.
    +
* No DELETE CASCADE and ON UPDATE CASCADE
   −
== Using Database triggers ==
+
Such SQL instructions are forbidden because they bypass the business rules of the application bringing a lot of troubles out of control of the developers. For example, if there is a delete cascade between table A and B, when the application will execute the code to remove a record in A, the children into table B will also be removed (this is the goal). But if there was a Dolibarr PHP trigger (for example provided by an external module) on the deletion of record of B (for example to validate the deletion or to execute a complementary action), the DELETE CASCADE will be executed without having the Dolibarr PHP trigger on B deletion executed, missing the validation or actions of the PHP trigger of the module. All business rules must be implemented on the same side (so the server PHP side), this is the reason why business rules implemented on Database side (like with delete cascade) are not allowed (same conclusion than the next point about Database triggers).
 +
Note that an external module can use them for links between Dolibarr tables and its module tables but a better recommended solution is to use the trigger to implement the deletion of its tables when a record of a parent official table is deleted.
 +
 
 +
==Using Database triggers==
 
Using Database triggers is not allowed into the code of Dolibarr, so you should have no troubles with Database triggers.  
 
Using Database triggers is not allowed into the code of Dolibarr, so you should have no troubles with Database triggers.  
Database triggers are not portable. Compatibility may be broken when upgrading your version of your database, database triggers are often not included into backup tools, database triggers need permissions that you may not have (and should not have) on a common secured database, database triggers means also that you include some "business rules" of your application into the database where most other rules are managed by the code itself creating conflict or code that can be debugged with a step by step debugger, ...
+
* Database triggers are not portable. Compatibility may be broken when upgrading your version of your database
There is tons of reason why we do not recommand to use database triggers in your own development.  
+
* Database triggers are often forgotten into backup tools
Using Database triggers can save you time when you make development but is a very bad practive that is forbidden. External modules that uses Database triggers may also be refused on the market place since it won't work for more than 50% of users.
+
* Database triggers need special permissions that you may not have (and should not have) on a common secured database
 +
* Database triggers means also that you include some "business rules" of your application into the database where most other rules are managed by the code itself (this can create conflict or code that can't be debugged with a step by step debugger)
 +
* Database triggers execute code using the timezone, the default language of the database instead of the reference timezone of the application that may be different (generating offset into database) and the default language setup in application ...
 +
There is tons of reasons why we do not recommend to use database triggers in your own development.
 +
 
 +
Using Database triggers can save you time when you make development but is a very bad practice when quality, portability, security and sustainability are priority, so it is forbidden as it is not compatible with the high level of quality of code expected by Dolibarr team. External modules that uses Database triggers may also be refused on the market place since it won't work for more than 50% of users.
   −
==Mysql specificities==
+
==MySQL-MariaDB specificity==
    
*Tables must be declared with format InnoDB.
 
*Tables must be declared with format InnoDB.
Line 330: Line 350:  
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.
 
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.
+
*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)
+
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)
<source lang="ini">
+
{{MySqlStrictMode}}
sql-mode="STRICT_ALL_TABLES,ONLY_FULL_GROUP_BY,NO_ZERO_DATE"
  −
</source>
      
==PostgreSQL specificities==
 
==PostgreSQL specificities==
Only Mysql SQL files must be maintained.
+
Only MySQL files must be maintained.
Those files are converted "on the fly" by the database Dolibarr driver.
+
Those files are converted "on the fly" by the database Dolibarr driver into the targeted database system.
   −
There is an exception for the SQL "UPDATE FROM":  
+
There is an exception for the SQL "UPDATE FROM" of Postgres:  
    
MySQL Syntax:
 
MySQL Syntax:
<source lang="sql">
+
<syntaxhighlight lang="sql">
UPDATE table_taget as target, table_source as source SET fieldtarget=source.fieldsource
+
UPDATE table_target as target, table_source as source SET fieldtarget=source.fieldsource
 
WHERE source.rowid=target.rowid;
 
WHERE source.rowid=target.rowid;
</source>
+
</syntaxhighlight>
    
PgSQL Syntax:
 
PgSQL Syntax:
<source lang="sql">
+
<syntaxhighlight lang="sql">
UPDATE table_taget as target SET fieldtarget=source.fieldsource
+
UPDATE table_target as target SET fieldtarget=source.fieldsource
 
FROM table_source as source WHERE source.rowid=target.rowid;
 
FROM table_source as source WHERE source.rowid=target.rowid;
</source>
+
</syntaxhighlight>
   −
There is no native SQL requests "UPDATE FROM" in all Dolibarr core. But if you use one in your own code of your module, you should do :
+
There is no native SQL requests "UPDATE FROM" in all Dolibarr core (only simple "UPDATE") so this difference of syntax is not a problem. But if you need to use one in your own code of your module, you should do :
<source lang="php">
+
<syntaxhighlight lang="php">
 
if ($this->db->type=='pgsql') {
 
if ($this->db->type=='pgsql') {
$sql="UPDATE table_taget as target SET fieldtarget=source.fieldsource
+
$sql="UPDATE table_target as target SET fieldtarget=source.fieldsource
 
FROM table_source as source WHERE source.rowid=target.rowid";
 
FROM table_source as source WHERE source.rowid=target.rowid";
 
} else {
 
} else {
$sql= "UPDATE table_taget as target, table_source as source SET fieldtarget=source.fieldsource
+
$sql= "UPDATE table_target as target, table_source as source SET fieldtarget=source.fieldsource
 
WHERE source.rowid=target.rowid";
 
WHERE source.rowid=target.rowid";
 
}
 
}
</source>
+
</syntaxhighlight>
    
=HTML norms=
 
=HTML norms=
Line 374: Line 392:     
For example:
 
For example:
<source lang="php">
+
<syntaxhighlight lang="php">
 
print '<a href="'.dol_buildpath('/mydir/mypage.php').'">'.img_picto('Texte alt','namepictopng','').'</a>';
 
print '<a href="'.dol_buildpath('/mydir/mypage.php').'">'.img_picto('Texte alt','namepictopng','').'</a>';
</source>
+
</syntaxhighlight>
    
*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".
 
*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".
Line 384: Line 402:  
*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"
 
*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"
   −
<source lang="php">
+
<syntaxhighlight lang="php">
 
if ($conf->use_javascript_ajax) {
 
if ($conf->use_javascript_ajax) {
 
...  // php code generating javascript here
 
...  // php code generating javascript here
 
}
 
}
</source>
+
</syntaxhighlight>
    
*Popup windows must not be used, except for tooltips (and must have a condition as explained before).
 
*Popup windows must not be used, except for tooltips (and must have a condition as explained before).
Line 394: Line 412:  
*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.
 
*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.
   −
==External templating framework ?==
+
==External template framework ?==
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.
+
There is ton of frameworks to provide a template language of an HTML page (smarty, twig, ...). The best and faster one we know is just called "PHP", and because Dolibarr already depends on PHP, using another framework may introduce a dependency on a third language to make templates.
 +
 
 +
We must remind that template frameworks are just pre-processor, BEFORE PHP. They can't be faster than PHP alone. When we say "speed is increased because template framework use a cache", you must read in fact "the overtime spent by the framework is reduced by cache, not the speed of processing a PHP template file". We should say "any template framework has an overload over PHP, the cache of the template framework just reduces the slowing effect of using this framework", but not "the speed is increased, it is always decreased compared to pure PHP".
 +
 
 +
Above all, having a template system is completely possible by doing a "'''.tpl.php'''" file that dos not contains any logic code, but only HTML and print of variables. 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 automatically 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.
 +
 
 +
But an external template system can keep a guarantee of 100% of isolation between code and HTML output. Yes, but this is interesting in only 1 situation: When teams building design is completely 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 are often be same, Ajax will be more and more present, even if we hope not "too much", and screens are more and more dependent of dynamic or contextual events, difficult to have this with one simple template without transforming the template into a page with high level of code). Also design is more and more managed now in CSS side instead of PHP side.
 +
 
 +
In the past, Dolibarr has experimented a template system like Smarty. If on the paper the idea was good, we quickly realized that finding a bug become a headache, coding become a treasure hunt, maintenance and development were done so slower (compared to previous situation where output templates are into the "View" section of each PHP file) that we forgot this idea. Pragmatism won.
   −
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 the templating framework just reduces the slowing effect of using this framework", but not "the speed is increased, it is always decreased compared to pure PHP".
+
There is a ton of other reasons to not use an external template system and all arguments to use them are also the best argument to use HTML/PHP as our template system. We just ask to keep separation between '''Controllers''' (the first section of PHP pages juste after the "/* Action" line), and the '''Views''', the second section after the "/* View" comment that is the presentation section build with 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.
+
=CSS norms=
   −
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).
+
==Syntax rules==
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 one simple 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.
+
There is NO syntax rules or norms for CSS. During Dolibarr Devcamp 2024/06 (Montpellier) a collective proposition was made to respect classes name from tailwindcss.
 +
 
 +
The objective is not to include tailwindcss into Dolibarr but to use syntax and classes norms issued from tailwindcss.
 +
 
 +
See [[migrating to tailwindcss css norms]]
 +
 
 +
PR: (link to PR to become)
    
=Dolibarr norms and code skeleton=
 
=Dolibarr norms and code skeleton=
Line 422: Line 454:  
Use it as an example.
 
Use it as an example.
 
Note that the skeletons are also used by the PHP Code generator ([[Module ModuleBuilder]]), which is described in the development chapter of Dolibarr modules, to speed up your development.
 
Note that the skeletons are also used by the PHP Code generator ([[Module ModuleBuilder]]), which is described in the development chapter of Dolibarr modules, to speed up your development.
 +
 +
== Global variables==
 +
There is some information or objects that is common and fixed for all the execution of a PHP page and need to be used nearly everywhere in the code. To avoid to propagate everywhere this
 +
information or objects, they are stored into global variables that you can access everywhere. This global variables are initialized at the begin of each PHP call (into the master.inc.php or master.inc.php or in top of the page). These objects are instantiated only once. The list of available global variables is defined here:
 +
{{Template:TemplateGlobalVariables}}
 +
 +
* $conf is the object that contains the configuration (saved into database)
 +
* $user is the object of the current user
 +
* $mysoc is the object with all information about the current company
 +
* $langs is the object with the current language
 +
* $db is the database handler of the current database connexion (inside classes, we should pass it in the constructor and we should get it with $this->db)
 +
* $hookmanager is a factory class to manage hooks
 +
* $extrafields is a factory class to manage extrafields
    
==Dates and Timezones==
 
==Dates and Timezones==
Line 447: Line 492:  
With PHP, like other languages (Java for exemple), non integer data (float, real, double) are not reliable for calculation.
 
With PHP, like other languages (Java for exemple), non integer data (float, real, double) are not reliable for calculation.
 
Try to make for example  
 
Try to make for example  
<source lang="php">
+
<syntaxhighlight lang="php">
 
print 239.2 - 229.3 - 9.9;
 
print 239.2 - 229.3 - 9.9;
</source>
+
</syntaxhighlight>
 
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.
 
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).
 
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).
<source lang="php">
+
<syntaxhighlight lang="php">
 
print price2num(239.2 - 229.3 - 9.9, 'MT');
 
print price2num(239.2 - 229.3 - 9.9, 'MT');
</source>
+
</syntaxhighlight>
 
If data manipulated is not an amount, then using MU, MT, MS has no sense, and you must use the function '''round()'''.
 
If data manipulated is not an amount, then using MU, MT, MS has no sense, and you must use the function '''round()'''.
   Line 463: Line 508:  
==Comparing version==
 
==Comparing version==
 
If your code need to make different things depending on Dolibarr version, you can use the following tip to detect and compare versions
 
If your code need to make different things depending on Dolibarr version, you can use the following tip to detect and compare versions
<source lang="php">
+
<syntaxhighlight lang="php">
 
$version=preg_split('/[\.-]/',DOL_VERSION);
 
$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 (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)
</source>
+
</syntaxhighlight>
    
But this solution need to include the function versioncompare. An alternative solution to test version is to do:
 
But this solution need to include the function versioncompare. An alternative solution to test version is to do:
<source lang="php">
+
<syntaxhighlight lang="php">
 
if ((float) DOL_VERSION >= 5.0) { //mycode for 5.0 only; } // For dolibarr 5.0.*
 
if ((float) DOL_VERSION >= 5.0) { //mycode for 5.0 only; } // For dolibarr 5.0.*
</source>
+
</syntaxhighlight>
    
==Logs==
 
==Logs==
 
Add logs to your code using function
 
Add logs to your code using function
<source lang="php">
+
<syntaxhighlight lang="php">
 
dol_syslog($yourmessage, LOG_INFO|LOG_DEBUG|LOG_WARNING|LOG_ERR);
 
dol_syslog($yourmessage, LOG_INFO|LOG_DEBUG|LOG_WARNING|LOG_ERR);
</source>
+
</syntaxhighlight>
    
==Working directory==
 
==Working directory==
If you need to create a working directory, into your code, refer to it with  
+
If you need to create a working directory for persistent data, into your code, refer to it with  
 
'''DOL_DATA_ROOT.'/mymodule''''
 
'''DOL_DATA_ROOT.'/mymodule''''
    
The directory can be created into your code by the following function:
 
The directory can be created into your code by the following function:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$mymoduledir=DOL_DATA_ROOT.'/mymodule';
 
$mymoduledir=DOL_DATA_ROOT.'/mymodule';
 
dol_mkdir($mymoduledir);
 
dol_mkdir($mymoduledir);
</source>
+
</syntaxhighlight>
    
If you need a directory to store temporary data, this directory must be '''DOL_DATA_ROOT.'/mymodule/temp''''
 
If you need a directory to store temporary data, this directory must be '''DOL_DATA_ROOT.'/mymodule/temp''''
Line 532: Line 577:  
*The '''Table And Row Data Gateway'''
 
*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.
+
This is the most simple. You have one class per table and each class is a link to the table with CRUD methods (Create, 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''' (http://ibatis.apache.org/).
 
Example: This mode is used by some ORM Frameworks, like '''iBatis''' (http://ibatis.apache.org/).
Line 538: Line 583:  
*The '''Active Record'''
 
*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.
+
Same as previous, but we are allowed to add some business functions into the CRUD class, if such functions are dedicated to manipulate data into this table.
 +
This pattern is known to be the most productive of the 3, as it is a very good compromise between the '''Table And Row Data Gateway''' and the '''Data Mapper'''.
   −
Example: This mode is used for Dolibarr development and most PHP softwares that include their own framework and best practices.
+
Example: This mode is used for Dolibarr development and most PHP software that includes their own framework and best practices.
    
*The '''Data Mapper'''
 
*The '''Data Mapper'''
Line 547: Line 593:  
More "purist" on paper (closer of business), this method also has the disadvantage of being more complex in practice.
 
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''' (http://propel.phpdb.org/trac/). We find this model in heavier applications, based on this ORM among others.
+
Example: This is the choice if you use the ORM Framework '''Propel''' (https://propelorm.org/). 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).
+
-> For Dolibarr development, the pattern used is 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 behavior 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).
 
[[Category:Development]]
 
[[Category:Development]]
 +
 +
==Design pattern for Graphic interfaces (MVC)==
 +
There are several design patterns for user interfaces (MVC, MVP, MVVM).
 +
Many confuse this design pattern with that of using a template framework. It's unrelated. A template framework (see previous point) is only linked to the "V" part of these models.
 +
 +
-> Dolibarr uses the '''MVC''' Design Pattern (Model - View - Controller). Part M (Model) is materialized by a DAO class containing the CRUD (Create-Read-Update-Delete) methods for accessing the table.
 +
Many frameworks have chosen to put the C code part (Controller) and the V code part (View) in different files, or even separate directories. The maintenance seems easier on paper, in practice, you have to juggle constantly between a ton of files when your project become larger. In Dolibarr, the separation is made by a simple comment section. The part of code C (Controller) is therefore the code that follow the tag
 +
<pre>
 +
/*
 +
* Actions
 +
* /
 +
</pre>
 +
It is followed by the part of code V (Vue), well separated from the C by the tag
 +
<pre>
 +
/*
 +
* View
 +
* /
 +
</pre>
 +
 +
This mode of separation has had a neutral effect on new developments but has made it possible to reduce the time required for maintenance and fixing bugs so significantly that it is not envisaged to switch to another type of separation (yes the findings made in real life are not always those expected by school theory). The important thing remains that there is always a separation between C and V, no matter how this separation is materialized, as long as it is (Reminder: Do not confuse the notion of separation of C and V with the notion of separation of the code to feed the data of a View and of the code to format the View, this a principle of separation relating to the Design Pattern of Vue, see next point).
 +
 +
==Design pattern for Views - Presentation==
 +
There are frameworks that specialize in Vue Design Pattern, the part of formatting the screen seen by the user.
 +
Old versions of Dolibarr used Smarty. This framework is an overlay to an already very powerful screen formatting framework called ... PHP. The benefits being much lower than the disadvantages, it has been abandoned in favor of PHP templates (.php files or .tpl.php files) which remain the simplest, the most universal, the most scalable and the most efficient, of the presentation frameworks we tried to use.
 +
 +
Its only flaw is that it cannot guarantee 100% the deviations of a developer who would integrate into the View, a logic unrelated to the presentation (therefore other than code intended for generation, formatting, HTML output , test or loop logic). This point is however controlled by the validation mechanism of the project managers' code.
 +
 +
Here again, it is the experience and the observation of a better productivity performance that has motivated the abandonment of the external Presentation frameworks in favor of the PHP framework.
 +
 +
=Rules to define branch target of a contribution=
 +
The rules to decide if a contribution (Pull Request) must be pushed into an old maintenance branch or into development are not strict and are often defined by the good sense (pragmastism first). We can how
 +
be help by this [[Decision algorithm for branch choice of a PR]].