Difference between revisions of "Language and development rules"

From Dolibarr ERP CRM Wiki
Jump to navigation Jump to search
Tags: Mobile web edit Mobile edit Advanced mobile edit
 
(295 intermediate revisions by 22 users not shown)
Line 1: Line 1:
 +
<!-- BEGIN origin interlang links -->
 +
<!-- You can edit this section but do NOT remove these comments
 +
    Links below will be automatically replicated on translated pages by PolyglotBot -->
 +
[[de:Sprachen_und_Programmierregeln]]
 +
[[fr:Langages_et_normes]]
 +
[[es:Lenguaje_y_Normas_de_Desarrollo]]
 +
[[zh:语言和开发规则]]
 +
<!-- END interlang links -->
 +
 
{{TemplateDocDevEn}}
 
{{TemplateDocDevEn}}
{{ToTranslate}}
 
  
Voici les quelques règles sur le langage, la syntaxe et normes de développement en vigueur pour le projet Dolibarr:
+
These are some rules on language, syntax and norm we use in the Dolibarr project:
 +
 
 +
=Versions=
 +
 
 +
*Current version of Dolibarr must work on:
 +
 
 +
#All OS (Windows, Linux, MACOS...)
 +
#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}}
 +
 
 +
=Copyright Norms=
  
 +
*All PHP files must start with a header that looks like
  
= Versions =
+
<syntaxhighlight lang="php">
 +
<?php
 +
/* Copyright (C) YYYY John Doe  <email@email.com>
 +
*
 +
* Licence information
 +
*/
 +
...
 +
</syntaxhighlight>
 +
When you edit an existing file of project, you must add a Copyright line under others.
  
* Dolibarr doit fonctionner sur:
+
=PHP Norms=
# Tous OS (Windows, Linux, MACOS...)
+
==PHP==
# PHP 4.3 ou + (Doit fonctionner sans aucun module PHP complémentaire hors les modules d'accès base de donnée).
 
# Mysql 3.1 ou +
 
  
= Normes PHP =
+
*Dolibarr is written with PHP and supports all versions of PHP higher than {{PHPMinVersion}}. All files must end with extension .php
  
* Dolibarr est écrit en PHP et supporte toutes versions PHP supérieures à la 4.1. Les fichiers doivent tous comporter l'extension .php
+
*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..
  
* L'appel aux variables superglobales PHP doit passer par les opérateurs dédiés $_GET, $_POST, $_COOKIES, $_SERVER, $_ENV.
+
*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).
Les autres opérateurs ($HTTP_SERVER_GET, ...) ayant été passés en deprecated au sein de PHP, ne doivent plus être utilisés. Ainsi le code doit fonctionner y compris quand l'option '''register_long_arrays''' est à off.
 
De plus, le code doit fonctionner quand l'option PHP '''register_globals''' est à off (recommandé par PHP) aussi bien que quand l'option '''register_globals''' à on (par défaut sur de nombreuses installations).
 
  
* Les smart tags PHP ne sont pas utilisés. Les sections de code doivent commencer par '''<?php'''
+
*Do not use '''<s>PHP_SELF</s>'''. Use instead '''$_SERVER["PHP_SELF"]'''. Also, Dolibarr framework sanitizes content of $_SERVER["PHP_SELF"] variable (into main.inc.php file, before any business code).
  
* Pas d'utilisation de la variable '''PHP_SELF'''. Utiliser a la place $_SERVER["PHP_SELF"]
+
*When several variables must be initialized with the same value, you must use individual declarations (separated by ;)
  
* Quand plusieurs variables doivent être initialisées avec la même valeur, il faut utiliser plusieurs lignes
+
<syntaxhighlight lang="php">
<pre>
+
$var1 = 1;
$var1=1;$var2=1;$var3=1;
+
$var2 = 1;
</pre>
+
$var3 = 1;
plutôt que
+
</syntaxhighlight>
<pre>
+
instead of
$var1=$var2=$var3=1;
+
<syntaxhighlight lang="php">
</pre>
+
$var1 = $var2 = $var3 = 1;
qui est moins performant.
+
</syntaxhighlight>
 +
which is slower.
 +
 
 +
<br />
 +
 
 +
====Strings====
 +
 
 +
*Strings must be have variables outside of the quote.
 +
 
 +
<syntaxhighlight lang="php">
 +
print "My text show my ".$variable." !\n";
 +
</syntaxhighlight>
 +
 
 +
<br />
  
* Les chaines doivent être encadrés de simple quote et les variables sorties de la chaine.
+
====Comments====
<pre>
 
print 'Mon texte affiche ma '.$variable.' !';
 
</pre>
 
  
* Les commentaires doivent suivre la syntaxe C, ie un double antislash pour un commentaire d'une ligne et utilisation slash-étoile pour ouvrir un bloc de plusieurs lignes
+
*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
<pre>
 
  
/* Bloc de commentaire
+
<syntaxhighlight lang="php">
 +
/* Bloc of comment
 
  *
 
  *
  * Fin du bloc
+
  * End of bloc
 
  */
 
  */
  
Line 50: Line 83:
 
$result=$monobjet->fetch($idobject);
 
$result=$monobjet->fetch($idobject);
  
for ($i = 1 , $i < 2 ; $i++)
+
for ($i = 1 , $i < 2 ; $i++) {
 +
    // comment on one line
 +
    print $i;
 +
}
 +
 
 +
function myFunction($param1, $param2)
 
{
 
{
  // commentaire sur une ligne
+
    // code
  print $i;
 
 
}
 
}
 +
</syntaxhighlight>
 +
 +
<br>
 +
 +
*Functions must return a value equal or higher than 0 if successful and strictly lower than 0 if error.
 +
 +
*No dead code (code never used) into Dolibarr core code (code used by external modules only must be included within the external modules).
 +
 +
*Use "include_once" for anything with functions or class definitions in it (like *.class.php and *.lib.php files), use "include" for template-style php with files containing common part of HTML and PHP code (like *.inc.php and *.tpl.php files).
  
</pre>
+
*Coding style to use is the '''PSR-12''' (https://www.php-fig.org/psr/psr-12/). Only rules tagged "MUST" are currently required. So note however the exceptions:
 +
**Length of line: PSR-12 mention we can't go up to 120 characters on same line, this is a soft limit. It is better to have long lines instead of long page with code content that is just data declaration and does not contain any logic. However, we introduced a hard limit of '''1000''' characters (having line larger than this may return errors on Continuous Integration tests).
 +
**Tabs are allowed: The other exception is that we don't replace systematically the tabs with spaces. Using tabs is more convenient for most editors/developers. Also using spaces breaks some auto-format features (like Eclipse autoformat feature on some Eclipse version). For the moment, the best setup is "Keep spaces/tabs as it is", however, you can activate the option "Remove spaces at end of lines".
 +
**We allow elements of an array on same line and we do not always add a new line at end of each element.
 +
**Note 1: The following rule are very important to follow:
 +
***Files must be saved with Unix format (LF) and not Windows (CR/LF). Unix format is compatible on all OS such as Unix like, Windows, Mac, but the Windows text file format may not work on some PHP under Unix.
 +
***Smart tags PHP are not used. PHP code section must start with '''<?php'''
 +
**Note 2: You can use the file '''dev/setup/codesniffer/ruleset.xml''' as rule file to control coding style with PHPCodeSniffer.
 +
**Note 3: You can use the file '''dev/setup/eclipse/PSR-12 [built-in].xml''' as rule file to setup Eclipse syntax formater.
 +
**Note 4: You may note that current code is not yet compliant with the PSR-12. The reason is that we must "now" follow the "MUST" rules of PSR-12, but it wasn't in the past. So feel free to change coding style to match new rules (keep in mind the 2 exceptions) if you find such cases.
 +
 
 +
==Classes and properties structures==
 +
 
 +
Some properties of classes are found in different classes. To avoid having different names, we will use the following PHP properties names:
 +
 
 +
{{TemplatePHPFields}}
 +
 
 +
=SQL and Database rules=
 +
 
 +
==DDL file format==
 +
Files containing definition of the database structure (DDL files) must be '''2 per table''':
 +
 
 +
*The first file defines the table and its fields. The file name contains the table name, e.g. like this: <tt>llx_''mytable''.sql</tt>
 +
 
 +
A comment will be added for each field to explain its usage.
 +
 
 +
*The second file defines all foreign keys, performance indexes or other constraints and the file name will be like: <tt>llx_''mytable''.key.sql</tt>
 +
 
 +
These files must be stored in the directory '''install/mysql/tables''' for all standard files or '''mymodule/tables''' for tables provided by an external module.
 +
 
 +
Example: '''file for creating the table llx_mytable will be llx_mytable.sql''':
 +
 
 +
<syntaxhighlight lang="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;
 +
</syntaxhighlight>
 +
 
 +
Example: '''file for creating keys/indexes for the table llx_mytable will be llx_mytable.key.sql''':
 +
 
 +
<syntaxhighlight lang="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);
 +
</syntaxhighlight>
 +
 
 +
==Table and fields structures==
 +
 
 +
*Structure of tables.
 +
 
 +
When you create a new table, it is recommended to use the same conventions as other Dolibarr tables. This means the following fields:
 +
- rowid INTEGER AUTO_INCREMENT PRIMARY KEY    that is technical id of record
 +
- entity INTEGER default 1                    that is id for the multicompany feature
 +
- date_creation  datetime                    that is the creation date
 +
- 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)
 +
- import_key      varchar(32)                that will contain the import code YYYYMMDDHHMMSS if you make mass import
 +
- status          smallint                    to store a status
 +
Optional
 +
- 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_valid integer that is the id of user validating record (if applicable)
 +
- fk_soc integer that is the ID of the Third party (if applicable)
 +
 
 +
Note field:
 +
- note_private text for private comment of the object
 +
- note_public text for public comment of the object
 +
or
 +
- note text if there is no need to have private and public
 +
 
 +
 
 +
*Type of 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)
 +
- smallint for a small number (like for a status) or a boolean
 +
- double(24,8) for any amount
 +
- double(6,3) for any vat rate
 +
- real for a quantity
 +
- varchar for a string (also if length is 1, the type char is more and more deprecated)
 +
- timestamp for a field date+time that is automatically updated
 +
- datetime for a field date+time
 +
- date for a field date
 +
- text or medium text for large fields (no index allowed on such fields)
 +
 
 +
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 types are not allowed for compatibility reasons.
 +
 
 +
*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==
 +
The primary key of a table must be called <tt>rowid</tt>.
 +
 
 +
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==
 +
Name of a foreign key must start with the prefix <tt>fk_</tt> followed by the table name (i.e. name of the referencing table) (this is required to avoid duplicate names in project that are not allowed even if in different tables by some DBMS like Postgresql) then the field name (i.e. name of the referencing field) (this is to allow to have several foreign keys in same table).
 +
 
 +
Example:
 +
''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 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==
 +
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:
 +
''uk_societe_code_client'' is a unique key on table llx_societe on field code_client.
 +
 
 +
==Index performance==
 +
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 <tt>idx_</tt> 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 Coding rules==
  
* Les fichiers doivent etre sauvés en format Unix (LF) et non Windows (CR/LF). Le format Unix étant compatible sur les OS Unix like, Windows, Mac, alors que le format fichier texte Windows pose problème sous certains PHP sous Unix.
+
*Alias usage/Fields naming
  
Les fonctions doivent retourner 0 en cas de succès, et un nombre <0 en cas d'erreur.
+
When doing select, we can use alias to simplify writing/reading of requests:
A ce jour, très peu de fonctions respectent ce standard mais c'est celui vers lequel il faut tendre.
+
<syntaxhighlight lang="sql">
 +
select chp1, chpxxx2 as chp2 from table2 as t1, table2 as t2 where t1.chpx = t2.chpy
 +
</syntaxhighlight>
 +
However, we must not used alias for update request as they are not compatible with Mysql 3.1.
  
= Normes SQL =
+
*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:
  
* Les SELECT * sont interdits ! Chaque SELECT doit spécifier la liste complète des champs à récupérer. Cela permet d'éviter les confusions. Exemple:
+
<syntaxhighlight lang="sql">
<pre>
 
 
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'
</pre>
+
</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 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:
 +
<syntaxhighlight lang="sql">
 +
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')
 +
</syntaxhighlight>
 +
 
 +
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)
 +
 
 +
* 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:
 +
<syntaxhighlight lang="php">
 +
$sql="SELECT rowid FROM table where datefield = NOW()";
 +
</syntaxhighlight>
 +
but do:
 +
<syntaxhighlight lang="php">
 +
$sql="SELECT rowid FROM table where datefield = '".$this->db->idate(dol_now())."'";
 +
</syntaxhighlight>
 +
 
 +
For example, don't do:
 +
<syntaxhighlight lang="php">
 +
$sql="SELECT rowid FROM table where DATEDIFF(table.datefield, NOW()) > 7";
 +
</syntaxhighlight>
 +
but do:
 +
<syntaxhighlight lang="php">
 +
$sql="SELECT rowid FROM table where datefield < '".$this->db->idate(dol_now() - (7 * 24 * 3600))."'";
 +
</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.
 +
 
 +
* 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.
 +
 
 +
* No DELETE CASCADE and ON UPDATE CASCADE
 +
 
 +
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.
 +
* Database triggers are not portable. Compatibility may be broken when upgrading your version of your database
 +
* Database triggers are often forgotten into backup tools
 +
* 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-MariaDB specificity==
 +
 
 +
*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)
 +
{{MySqlStrictMode}}
 +
 
 +
==PostgreSQL specificities==
 +
Only MySQL files must be maintained.
 +
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" of Postgres:
 +
 
 +
MySQL Syntax:
 +
<syntaxhighlight lang="sql">
 +
UPDATE table_target as target, table_source as source SET fieldtarget=source.fieldsource
 +
WHERE source.rowid=target.rowid;
 +
</syntaxhighlight>
  
* Dans les requêtes SQL, on quote les champs mais pas les numériques qui contiennent des montants destinés à être stockés dans des champs de type double ou real. Les quotes sur les numériques de type float provoquent parfois un stockage d'une valeur différente. Par exemple 412.62 dans le insert sera en fait stocké avec la valeur 412.61999512 en base si le champ est de type double(24,8). Et seul le PHP voit 412.61999512. Les autres outils verront 412.62 donnant l'impression qu'il n'y a pas de problème. Et c'est le PHP qui a raison, il y a problème en base. En supprimant les quotes sur les numériques, cela va mieux.
+
PgSQL Syntax:
 +
<syntaxhighlight lang="sql">
 +
UPDATE table_target as target SET fieldtarget=source.fieldsource
 +
FROM table_source as source WHERE source.rowid=target.rowid;
 +
</syntaxhighlight>
  
Exemple:
+
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 :
<pre>
+
<syntaxhighlight lang="php">
Bon:    INSERT INTO table_1 (field_txt, field_num) VALUES ('txt', 412.62)
+
if ($this->db->type=='pgsql') {
Mauvais: INSERT INTO table_1 (field_txt, field_num) VALUES ('txt', '412.62')
+
$sql="UPDATE table_target as target SET fieldtarget=source.fieldsource
</pre>
+
FROM table_source as source WHERE source.rowid=target.rowid";
 +
} else {
 +
$sql= "UPDATE table_target as target, table_source as source SET fieldtarget=source.fieldsource
 +
WHERE source.rowid=target.rowid";
 +
}
 +
</syntaxhighlight>
  
Remarque, le pb des float est général et pas seulement sur les accès base, il est présent dans tous les languages quand on travaille sur des nombres réels, aussi ils doivent être, dès que affectés, nettoyés par la fonction price2num avec le 2eme paramètre renseigné à:
+
=HTML norms=
'MU', 'MT' ou 'MS' selon le besoin. (voir doc fonction)
+
==Syntax rules==
  
Dans la pratique, afin d'etre compatible avec toutes les précisions des pays, on utilisera les types suivants:
+
*HTML used must be HTML compliant and not XHTML. All attributes of HTML tags must be in lower case and quoted with ".
- double(24,8) pour tout montant
 
- double(6,3) pour les taux de tva
 
- real pour une quantité
 
  
* Dolibarr doit fonctionner même avec l'option '''strict''' de Mysql active.
+
*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().
  
* Les fonctions NOW ou SYSDATE sont interdites au sein des ordres SQL. S'il faut saisir la date du moment dans un champ, la valeur doit venir du PHP et non du moteur de base de données. Ceci afin d'avoir une meilleure portabilité du code mais surtout d'avoir une gestion correcte des TimeZone.
+
For example:
 +
<syntaxhighlight lang="php">
 +
print '<a href="'.dol_buildpath('/mydir/mypage.php').'">'.img_picto('Texte alt','namepictopng','').'</a>';
 +
</syntaxhighlight>
  
= Normes HTML =
+
*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".
  
* Tous les attributs dans les balises HTML doivent être *en minuscule* et quotés avec des *doubles quote* (Norme xhtml)
+
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.
  
* Les liens href doivent être absolus et se baser pour les pages sur la constante DOL_URL_ROOT qui pointe sur htdocs et pour les images se base sur l'appel de la fonction img_picto.
+
*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"
Par exemple:
 
<pre>
 
print '<a href="'.DOL_URL_ROOT.'/monrep/mapage.php">'.img_picto('Texte alt','nompictopng','').'</a>';
 
</pre>
 
  
* Le javascript et l'appel aux scripts java dans les pages php est à proscrire. Si toutefois du code javascript est inclus, il doit être conditionné par le test sur "$conf->use_javascript"
+
<syntaxhighlight lang="php">
<pre>
+
if ($conf->use_javascript_ajax) {
if ($conf->use_javascript)
+
...  // php code generating javascript here
{
 
...  // Le code php qui génère du javascript est ici
 
 
}
 
}
</pre>
+
</syntaxhighlight>
 +
 
 +
*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.
 +
 
 +
==External template framework ?==
 +
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.
 +
 
 +
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).
 +
 
 +
=CSS norms=
 +
 
 +
==Syntax rules==
 +
 
 +
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=
 +
 
 +
==Skeleton code==
 +
To standardize the code, and to speed up the development of new components in Dolibarr, you'll find skeletons of files fully prepared in the directory '''htdocs/modulebuilder/templates/'''.
 +
 
 +
For example:
 +
 
 +
*1 file that serves as an example of the module description: '''myModule.class.php'''
 +
*1 file that serves as an example of code for creating a new class: '''skeleton_class.class.php'''
 +
*1 file that serves as an example of code for creating a new page: '''skeleton_page.php'''
 +
*1 file 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 ([[Module ModuleBuilder]]), which is described in the development chapter of Dolibarr modules, to speed up your development.
  
* Les popups windows ne doivent pas être utilisées, sauf pour des tooltips (et restent condtionnées par le point ci-dessus).
+
== 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}}
  
* Les scripts externes sont écrits en Perl s'ils ne peuvent l'être en php, l'utilisation d'un autre langage n'est pas interdit mais doit être discuté au préalable dans la mailing list des développeurs. Le langage devra être maitrisé par au moins 2 développeurs pour en assurer la maintenance.
+
* $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
  
= Normes Dolibarr et squelettes de code =
+
==Dates and Timezones==
 +
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:
  
== Squelettes de code ==
+
*A date into memory must always be stored with Timestamp GMT format.
Afin d'uniformiser le code et d'accélérer le développement de nouveaux composants dans Dolibarr, se trouvent dans le répertoire '''dev/skeletons''', 4 squelettes de code tout préparés.
+
*A date stored into database, is the GMT Timestamp of date submitted into the request usng the <b>PHP server</b> timezone. This does not apply to date update automatically by database (fields '''tms''' into database).
  
* 1 qui sert d'exemple de descripteur de module: '''myModule.class.php'''
+
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).
* 1 qui sert d'exemple de code pour faire une nouvelle classe: '''skeleton_class.class.php'''
 
* 1 qui sert d'exemple de code pour faire une nouvelle page: '''skeleton_page.php'''
 
* 1 qui sert d'exemple de code pour faire un script à exécuter en ligne de commande: '''skeleton_script.php'''
 
  
Servez-vous en comme exemple.
+
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).
Notons que ces squelettes sont aussi utilisés par le générateur de code PHP qui est décrit dans la chapitre de développement de module Dolibarr pour accélérer vos développement.
 
  
== Les dates et TimeZone ==
+
*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.
Dolibarr se veut une application mutli-utilisateur et multi-localisation. Il convient donc de stocker les dates dans le bon format. Pour éviter les problèmes de conversion, les règles suivantes doivent être appliquées:
 
* Une date en mémoire doit être au format Timestamp GMT.
 
* Une date stockée en base de données contient le Timestamp GMT en rapport avec la date soumise dans la requête en heure locale <b>du serveur PHP</b>. Cela ne concerne pas les dates mises à jour automatiquement par la base (champ '''tms''' en base).
 
Ainsi le 1er janvier 1970, 2 heures à Paris (TZ=+1) sera stocké en mémoire sera soumis à la base de données avec la chaine '19700101020000' (PHP convertit en heure de son TZ et la base déconvertit avec son TZ qui est le même que celui de PHP).
 
  
Les méthodes select doivent donc traduire les champs dates lus qui sont au format chaine TZ de la base ('19700101020000') par appel de la méthode db->jdate afin de récupérer une information en mémoire au format Timestamp GMT. Et les méthodes insert doivent lors de la génération de la requête convertir la date mémoire connue en variable, par la méthode db->idate (Voir exemples générés par le squelette).
+
*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'''.
  
* Les dates mises à jour automatiquement par la base (champ '''tms''' en base) contiennent le Timestamp GMT au moment où la modification est faite. Les méthodes select récupèrent directement cette donnée en mémoire au format Timestamp GMT.
+
==UTF8/ISO encoding==
 +
Dolibarr stores data in the following way:
  
== L'encodage UTF8/ISO ==
+
*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.
Dolibarr stocke les informations de manière suivante:
+
*Memory data is stored in UTF8 (PHP object instances).
* En base de donnée, les données sont en UTF8 ou ISO. Cela dépend du pagecode de la base, donc des options à la création de cette base. Dans tous les cas le driver d'accès base Dolibarr (dans /lib/database) s'arrange à la lecture et insertion pour convertir vers et depuis de l'UTF8.
+
*Web pages are returned and rendered into UTF8
* Les données en mémoires sont donc stockées en UTF8 (instances d'objets PHP).
 
* Les pages web affichées à l'écran sont au format UTF8 (avec les versions < 2.5.1, c'est le vieux paramètre $character_set du fichier conf.php qui définissait le format de sortie).
 
  
== Les nombres réels, montants et calculs ==
+
==Float numbers, amount and calculation==
En PHP comme dans d'autres langages (Java par exemple), les données non entières (float, real, double) ne sont pas fiables.
+
With PHP, like other languages (Java for exemple), non integer data (float, real, double) are not reliable for calculation.
Essayer de faire par exemple
+
Try to make for example
<pre>
+
<syntaxhighlight lang="php">
 
print 239.2 - 229.3 - 9.9;
 
print 239.2 - 229.3 - 9.9;
</pre>
+
</syntaxhighlight>
Vous n'obtiendrez pas zéro mais un nombre très petit en puissance de 10 négative. Si vous obtenez zéro, vous pourrez trouvez d'autres exemples qui ne fonctionnent pas.
+
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.
Le problème des float est général, une variable résultante de calcul de nombre réels doit SYSTEMATIQUEMENT être nettoyée par la fonction price2num avec le 2eme paramètre renseigné à: 'MU', 'MT' ou 'MS' selon le besoin (voir doc fonction).
+
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).
<pre>
+
<syntaxhighlight lang="php">
 
print price2num(239.2 - 229.3 - 9.9, 'MT');
 
print price2num(239.2 - 229.3 - 9.9, 'MT');
</pre>
+
</syntaxhighlight>
S'il ne s'agit pas d'un prix sur lequel s'adapte les paramètres MU, MT ou MS, il faut utiliser la fonction '''round()'''.
+
If data manipulated is not an amount, then using MU, MT, MS has no sense, and you must use the function '''round()'''.
 +
 
 +
==Creation of tables==
 +
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.
 +
 
 +
==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
 +
<syntaxhighlight lang="php">
 +
$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)
 +
</syntaxhighlight>
 +
 
 +
But this solution need to include the function versioncompare. An alternative solution to test version is to do:
 +
<syntaxhighlight lang="php">
 +
if ((float) DOL_VERSION >= 5.0) { //mycode for 5.0 only; } // For dolibarr 5.0.*
 +
</syntaxhighlight>
 +
 
 +
==Logs==
 +
Add logs to your code using function
 +
<syntaxhighlight lang="php">
 +
dol_syslog($yourmessage, LOG_INFO|LOG_DEBUG|LOG_WARNING|LOG_ERR);
 +
</syntaxhighlight>
 +
 
 +
==Working directory==
 +
If you need to create a working directory for persistent data, into your code, refer to it with
 +
'''DOL_DATA_ROOT.'/mymodule''''
 +
 
 +
The directory can be created into your code by the following function:
 +
<syntaxhighlight lang="php">
 +
$mymoduledir=DOL_DATA_ROOT.'/mymodule';
 +
dol_mkdir($mymoduledir);
 +
</syntaxhighlight>
 +
 
 +
If you need a directory to store temporary data, this directory must be '''DOL_DATA_ROOT.'/mymodule/temp''''
 +
 
 +
=Design patterns and Object programming=
 +
 
 +
==Creation design patterns (GoF)==
 +
Design patterns defined by the Gang Of Four (see wikipédia on [[w:Design patterns|Design patterns]]).
 +
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.
 +
 
 +
==Structure design patterns (GoF)==
 +
Design patterns defined by the Gang Of Four (see wikipédia on [[w:Design patterns|Design patterns]]).
 +
No usage of such patterns is required.
 +
 
 +
==Behavior design patterns (GoF)==
 +
Design patterns defined by the Gang Of Four (see wikipédia on [[w:Design patterns|Design patterns]]).
 +
No usage of such patterns is required.
 +
 
 +
==Design patterns of enterprise (Martin Fowler)==
 +
 
 +
===Patterns of code organization===
 +
[[w: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'''.
 +
 
 +
===Communication between business logic - data (ORM)===
 +
 
 +
There are 3 ways to make links:
 +
 
 +
*The '''Table And Row Data Gateway'''
  
== Création de tables ==
+
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.
Ne pas créer de table à l'utilisation, c'est-à-dire à la '''première utilisation''' du module.
 
Si vous créez un module qui utilise des tables qui ne sont pas intégrées en standard dans le code de Dolibarr, veillez à suivre le didacticiel Créer un module qui explique comment joindre des tables qui se créent à l'activation du module et non à son utilisation (voir plus haut).
 
  
== Logs ==
+
Example: This mode is used by some ORM Frameworks, like '''iBatis''' (http://ibatis.apache.org/).
Ajouter des traces dans votre code avec la fonction
 
dolibarr_syslog($yourmessage, LOG_INFO|LOG_DEBUG|LOG_WARNING|LOG_ERR);
 
  
== Répertoires de travail ==
+
*The '''Active Record'''
Si vous avez besoin de créer un répertoire de travail, dans votre code, faites référence à ce répertoire par
 
DOL_DATA_ROOT.'/monmodule'.
 
Le répertoire peut être créé dans votre code à l'exécution par le code suivant:
 
<pre>
 
$mymoduledir=DOL_DATA_ROOT.'/monmodule';
 
if (! is_dir($mymoduledir)) create_exdir($mymoduledir);
 
</pre>
 
  
Si vous avez besoin d'un répertoire qui contiendra des données temporaires, ce répertoire doit être DOL_DATA_ROOT.'/monmodule/temp'
+
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'''.
  
= Programmation Objet =
+
Example: This mode is used for Dolibarr development and most PHP software that includes their own framework and best practices.
  
== Motifs d'organisation du code: ==
+
*The '''Data Mapper'''
  
[[wikipedia:Martin Fowler|Martin Fowler]] a identifié 3 méthodes d'organisation du code appelées '''motifs''':
+
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.
* Le '''Transaction Script''' (Le code est linéraire en fonction d'une action utilisateur).
+
More "purist" on paper (closer of business), this method also has the disadvantage of being more complex in practice.
C'est le motif à l'ancienne utilisé dans les langages procéduraux.
 
Inconvénient: Redondance du code. Nécessité de connaitre le modèle physique pour développer.
 
* Le '''Domain Model'''
 
C'est un concept possible depuis les langages objets. Ce sont les procédures métiers (qui doivent être identifiés avant) qui servent de bases pour les classes objets.
 
Inconvénient: Motif complexe à maintenir.
 
* Le '''Table Module'''
 
Un intermédiaire entre les 2 précédents où l'on a une instance unique de classe par table de la base de données.
 
  
Comme le montre les squelettes de code (voir point précédent), Dolibarr se base sur le principe du '''Table Module'''.
+
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.
  
== Communication Logique métier - Données (ORM) ==
+
-> 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]]
  
Il existe 3 modes de liaisons:
+
==Design pattern for Graphic interfaces (MVC)==
* Le '''Table And Row Data Gateway'''
+
There are several design patterns for user interfaces (MVC, MVP, MVVM).
C'est le plus simple. On crée une table par classe et chaque classe est un pont avec la table correspondante, voir une classe par ligne de table. Une instance de classe étant alors un enregistrement de la table. La classe ne contient que du code d'accès aux lignes ou colonnes de tables.
+
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.
  
Exemple: C'est le mode mis en oeuvre quand on utilise certains Frameworks d'ORM comme '''iBatis''' (http://ibatis.apache.org/).
+
-> 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>
  
* Le '''Active Record'''
+
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).
Identique au précédent, mais on se permet d'ajouter quelques fonctions métiers sur la classe, à conditions que ces fonctions soient propres à la table ou à l'enregistrement.
 
  
Exemple: C'est le mode choisi pour les développements Dolibarr et de nombreuses autres applications PHP qui ont leur propre framework et pratiques de développements.
+
==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.  
  
* Le '''Data Mapper'''
+
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.
Les classes représentent les entités du problème et non les données. Il faut donc doubler, tripler... ces classes avec des classes Mapper pour accéder aux données.
 
Plus "puriste" sur le papier car plus proche du métier, ce mode a aussi l'inconvénient d'être plus complexe sur le plan pratique.
 
Exemple: C'est le choix si on utilise le Framework d'ORM '''Propel''' (http://propel.phpdb.org/trac/). On le trouve donc sur des applications plus lourdes basées sur cet ORM entre autres.
 
  
 +
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.
  
Pour les développements Dolibarr, il est recommandé d'utiliser le mode de liaison '''Active Record''' qui offre les avantages d'un modèle proche du métier sans en avoir la complexité et sans trop masquer non plus la technique. C'est dans ce mode que le développement, la compréhension du code et la maintenance technique et/ou fonctionnelle semble la plus productive (ceci est toutefois un éternel débat entre les puristes et les pragmatiques, débat dans lequel personne ne peut vraiment avoir raison, car tout dépend des objectifs à atteindre).
+
=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]].

Latest revision as of 13:32, 2 January 2025


These are some rules on language, syntax and norm we use in the Dolibarr project:

Versions

  • Current version of Dolibarr must work on:
  1. All OS (Windows, Linux, MACOS...)
  2. PHP 7.1.0+ (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.
  3. MySQL 5.1+

Copyright Norms

  • All PHP files must start with a header that looks like
<?php
/* Copyright (C) YYYY John Doe  <email@email.com>
 *
 * Licence information
 */
...

When you edit an existing file of project, you must add a Copyright line under others.

PHP Norms

PHP

  • Dolibarr is written with PHP and supports all versions of PHP higher than 7.1.0+. All files must end with extension .php
  • Usage of PHP superglobals variables must use the dedicated operators $_COOKIE, $_SERVER, $_ENV but use the Dolibarr functions 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).
  • Do not use PHP_SELF. Use instead $_SERVER["PHP_SELF"]. Also, Dolibarr framework sanitizes content of $_SERVER["PHP_SELF"] variable (into main.inc.php file, before any business code).
  • When several variables must be initialized with the same value, you must use individual declarations (separated by ;)
$var1 = 1;
$var2 = 1;
$var3 = 1;

instead of

$var1 = $var2 = $var3 = 1;

which is slower.


Strings
  • Strings must be have variables outside of the quote.
print "My text show my ".$variable." !\n";


Comments
  • 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
/* Bloc of comment
 *
 * End of bloc
 */

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

for ($i = 1 , $i < 2 ; $i++) {
    // comment on one line
    print $i;
}

function myFunction($param1, $param2)
{
    // code
}


  • Functions must return a value equal or higher than 0 if successful and strictly lower than 0 if error.
  • No dead code (code never used) into Dolibarr core code (code used by external modules only must be included within the external modules).
  • Use "include_once" for anything with functions or class definitions in it (like *.class.php and *.lib.php files), use "include" for template-style php with files containing common part of HTML and PHP code (like *.inc.php and *.tpl.php files).
  • Coding style to use is the PSR-12 (https://www.php-fig.org/psr/psr-12/). Only rules tagged "MUST" are currently required. So note however the exceptions:
    • Length of line: PSR-12 mention we can't go up to 120 characters on same line, this is a soft limit. It is better to have long lines instead of long page with code content that is just data declaration and does not contain any logic. However, we introduced a hard limit of 1000 characters (having line larger than this may return errors on Continuous Integration tests).
    • Tabs are allowed: The other exception is that we don't replace systematically the tabs with spaces. Using tabs is more convenient for most editors/developers. Also using spaces breaks some auto-format features (like Eclipse autoformat feature on some Eclipse version). For the moment, the best setup is "Keep spaces/tabs as it is", however, you can activate the option "Remove spaces at end of lines".
    • We allow elements of an array on same line and we do not always add a new line at end of each element.
    • Note 1: The following rule are very important to follow:
      • Files must be saved with Unix format (LF) and not Windows (CR/LF). Unix format is compatible on all OS such as Unix like, Windows, Mac, but the Windows text file format may not work on some PHP under Unix.
      • Smart tags PHP are not used. PHP code section must start with <?php
    • Note 2: You can use the file dev/setup/codesniffer/ruleset.xml as rule file to control coding style with PHPCodeSniffer.
    • Note 3: You can use the file dev/setup/eclipse/PSR-12 [built-in].xml as rule file to setup Eclipse syntax formater.
    • Note 4: You may note that current code is not yet compliant with the PSR-12. The reason is that we must "now" follow the "MUST" rules of PSR-12, but it wasn't in the past. So feel free to change coding style to match new rules (keep in mind the 2 exceptions) if you find such cases.

Classes and properties structures

Some properties of classes are found in different classes. To avoid having different names, we will use the following PHP properties names:

- 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.

SQL and Database rules

DDL file format

Files containing definition of the database structure (DDL files) must be 2 per table:

  • The first file defines the table and its fields. The file name contains the table name, e.g. like this: llx_mytable.sql

A comment will be added for each field to explain its usage.

  • The second file defines all foreign keys, performance indexes or other constraints and the file name will be like: llx_mytable.key.sql

These files must be stored in the directory install/mysql/tables for all standard files or mymodule/tables for tables provided by an external module.

Example: file for creating the table llx_mytable will be 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;

Example: file for creating keys/indexes for the table llx_mytable will be 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);

Table and fields structures

  • Structure of tables.

When you create a new table, it is recommended to use the same conventions as other Dolibarr tables. This means the following fields:

- rowid INTEGER AUTO_INCREMENT PRIMARY KEY    that is technical id of record
- entity INTEGER default 1                    that is id for the multicompany feature
- date_creation   datetime                    that is the creation date
- 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)
- import_key      varchar(32)                 that will contain the import code YYYYMMDDHHMMSS if you make mass import
- status          smallint                    to store a status

Optional

- 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_valid integer that is the id of user validating record (if applicable)
- fk_soc integer that is the ID of the Third party (if applicable)

Note field:

- note_private text for private comment of the object
- note_public text for public comment of the object

or

- note text if there is no need to have private and public


  • Type of 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)
- smallint for a small number (like for a status) or a boolean
- double(24,8) for any amount
- double(6,3) for any vat rate
- real for a quantity
- varchar for a string (also if length is 1, the type char is more and more deprecated)
- timestamp for a field date+time that is automatically updated
- datetime for a field date+time
- date for a field date
- text or medium text for large fields (no index allowed on such fields)

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 types are not allowed for compatibility reasons.

  • 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 llx_. In a future version this value should be modified during installation process.

Primary keys

The primary key of a table must be called rowid.

Some old tables do not use this rule and use a primary key called id (e.g. Table llx_c_actioncomm). The reason is historical and this should not happen anymore.

Foreign keys

Name of a foreign key must start with the prefix fk_ followed by the table name (i.e. name of the referencing table) (this is required to avoid duplicate names in project that are not allowed even if in different tables by some DBMS like Postgresql) then the field name (i.e. name of the referencing field) (this is to allow to have several foreign keys in same table).

Example: 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 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

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 uk_, 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: uk_societe_code_client is a unique key on table llx_societe on field code_client.

Index performance

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 Coding rules

  • 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. And above all, this make reengeering of code easier and make impact analysis of change on a field possible. 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 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:

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, 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)

  • 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:

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

but do:

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

For example, don't do:

$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.

  • 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.

  • No DELETE CASCADE and ON UPDATE CASCADE

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.

  • Database triggers are not portable. Compatibility may be broken when upgrading your version of your database
  • Database triggers are often forgotten into backup tools
  • 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-MariaDB specificity

  • 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 files must be maintained. 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" of Postgres:

MySQL Syntax:

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

PgSQL Syntax:

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

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 :

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

HTML norms

Syntax rules

  • 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.

External template framework ?

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.

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).

CSS norms

Syntax rules

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

Skeleton code

To standardize the code, and to speed up the development of new components in Dolibarr, you'll find skeletons of files fully prepared in the directory htdocs/modulebuilder/templates/.

For example:

  • 1 file that serves as an example of the module description: myModule.class.php
  • 1 file that serves as an example of code for creating a new class: skeleton_class.class.php
  • 1 file that serves as an example of code for creating a new page: skeleton_page.php
  • 1 file 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 (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: $user, $conf, $db, $langs, $mysoc, $hookmanager, $extrafields

  • $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

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 encoding

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).
  • Web pages are returned and rendered into UTF8

Float numbers, amount and 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

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().

Creation of tables

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.

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

$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.*

Logs

Add logs to your code using function

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

Working directory

If you need to create a working directory for persistent data, 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'

Design patterns and Object programming

Creation design patterns (GoF)

Design patterns defined by the Gang Of Four (see wikipédia on Design patterns). 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.

Structure design patterns (GoF)

Design patterns defined by the Gang Of Four (see wikipédia on Design patterns). No usage of such patterns is required.

Behavior design patterns (GoF)

Design patterns defined by the Gang Of Four (see wikipédia on Design patterns). No usage of such patterns is required.

Design patterns of enterprise (Martin Fowler)

Patterns of code organization

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.

Communication between business logic - 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 (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/).

  • The Active Record

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 software that includes 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 (https://propelorm.org/). We find this model in heavier applications, based on this ORM among others.

-> 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).

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

/*
 * Actions
 * /

It is followed by the part of code V (Vue), well separated from the C by the tag

/*
 * View
 * /

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.