--- name: dolibarr-dev description: Use when developing Dolibarr ERP/CRM code, working with database queries, or asking about Dolibarr best practices. license: MIT user-invocable: true allowed-tools: - read_file - write_file - grep - bash --- # Dolibarr Development Best Practices (Core Agent Skill) This skill guides agents on developing, modifying, and debugging code within the Dolibarr ERP/CRM codebase while strictly adhering to professional standards, security guidelines, and the project's established architecture. ## 🛑 Core Principles: Non-Negotiable Mandatory Rules These principles must be followed even before reviewing specific task details. Violation of these principles results in failed suggestions. ### 🔒 Security & Data Integrity 1. **Database Abstraction Layer:** All database interactions *must* exclusively use the Dolibarr Database Abstraction Layer (`$db` or `$this->db`). **Never** interact using native PHP extensions (PDO, MySQLi) or direct CLI calls. 2. **Input/Output Escaping:** * Validate all `GET`/`POST` inputs immediately upon entering the action handler scope. * **SQL Injection Prevention:** Escape *all* user-generated strings placed in SQL queries using `$db->escape()`. For integers, use explicit casting: `((int) $var)`; for floats, use `(float) $var`. 3. **Variable Safety Naming:** When constructing dynamic SQL, the resulting variable holding the entire query string MUST be clearly prefixed (e.g., `$sqlWhereClause`, `$queryParams`). This pattern helps static analysis tools detect unsafe assignments. ### 💻 Code Structure & Quality 1. **Coding Standard:** All new and modified committed code must strictly adhere to **PSR-12** (enforcable by using `phpcbf` and `phpcs`).All properties and all function arguments and return value need detailed PHPDoc (e.g., `array`).Variables expected to exist in view files require both a PHPDoc declaration *and* the use of `'@phan-var-force';` declarations near the HEAD of the file for strict static analysis tracking. 2. **Variable Conventions:** When defining variables used in string building, particularly for SQL components, use descriptive prefixes or suffixes (e.g., `$sql_select`, `$actionSuffix`). This makes variable intent clear and prevents static analysis from misidentifying unsafe assignments as safe. 3. **Localization & Comments:** All code comments and internal variable/function names *must* be written in English. Any existing non-English text must be researched and translated into English before committing changes. 4. **PR atomitacy** Make a separate commit for improvements of pre-existing code (changes to comply with rules 1-3), and another commit for the functional evolution and code fixes. Do not apply rules 1-3 to existing code in backports (i.e., non-functional changes not applied to a (fork of) the develop branch. ### ⚙️ Workflow & Architecture 1. **Hooks First:** Before implementing any logic that runs on a core lifecycle event (e.g., form save, object update), check if an existing Dolibarr hook can be used. Use the standard calling pattern: `$hookmanager->executeHooks('actionName', $parameters, $object, $action);`. 2. **Action/View Separation:** Always clearly separate page action logic (executed on POST) from pure rendering (the HTML view). --- ## 🧭 Workflow and Tasks Guidance This section guides the agent through common development tasks. ### Code Investigation / Searching / Database analysis * Use `pre-commit` to run tools (`php-cbf`, `php-cs`, `shellcheck`, `php-lint` - example:`pre-commit run php-cbf --files RELATIVEFILEPATH`) when the git hook is installed as local direct installations differ accross systems. * **IMPORTANT**: Always use `git grep` instead of `find` for searching the codebase. `find` searches all directories including `.git` which is very slow. Use: ```bash git grep -n "pattern" -- "*.php" git grep -n "function_name" htdocs/core/lib/ -- "*.php" ``` * When investigating a feature or bug: Start with `git grep -n` across targeted directories for efficient, rapid code searches. Using `$db->prefix()` consistently is the first step in tracing data flows. * Dependency Check: Before modifying a file, search both `htdocs/core/lib/` and `htdocs/core/class/` to ensure similar methods or utilities are not already in use. Always check if the concern object extends `CommonObject`, favouring its built-in methods (`fetch()`, `create()`, `update()`, etc.). * Dolibarr Function & Method Arguments: Check the function signature before implementing a call - the parameter order is not consistent across dolibarr functions that have the same name. * When you need to access the database for analysis, use php, example: ```bash php <<'EOPHP' query('SELECT * FROM ' . $db->prefix() . 'actioncomm'); print_r($result); EOPHP ``` ### Module Development * **Module Template:** Use the structure found at `htdocs/modulebuilder/template/` as a definitive guide when initiating a new module. * **Hook Priority:** When adding functionality that interacts with core Dolibarr processes, check for existing hooks first to minimize architectural impact and maintain compatibility. ### Database Interaction Detail (Refined) This details the preferred mechanical steps: 1. **Read Operations:** Use `$db->query('SELECT ...')` followed by fetching results using methods like `$db->fetch_object()` or `$db->fetch_array()`. 2. **Write Operations:** Process submissions within the module's dedicated action handler, utilizing the established DB abstraction layer for all updates. ### Extrafields Best Practices **IMPORTANT**: When working with extrafields (custom fields), follow these patterns from the [Dolibarr Extrafields Wiki](https://wiki.dolibarr.org/index.php/Extrafields): #### Loading & Accessing Extrafields ```php $object->fetch(); // CommonObject fetch loads its extrafields // Access extrafield values via: $field_copy = $object->array_options['options_FIELDNAME'] ``` #### Saving Extrafields Before calling `$object->create()` or `$object->update()`, ensure extrafields are set: ```php // For form submissions: $ret = $extrafields->setOptionalsFromPost($extralabels, $object); // For direct assignment: $object->array_options['options_FIELDNAME'] = $value; // Then call update() - it will automatically save extrafields via insertExtraFields() $result = $object->update($user); ``` #### Displaying Extrafields In view pages: ```php print $object->array_options['options_FIELDNAME']; ``` In edit pages: ```php $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); if (empty($reshook) && !empty($extrafields->attribute_label)) { print $object->showOptionals($extrafields, 'edit'); } ``` #### Extrafields Table Structure Each CommonObject objecttype has its own extrafields table: ```sql llx_{objecttype}_extrafields - rowid (AUTO_INCREMENT PRIMARY KEY) - tms (timestamp) - fk_object (integer NOT NULL) - import_key (varchar) ``` #### Reference - [Dolibarr Extrafields Wiki](https://wiki.dolibarr.org/index.php/Extrafields) - [Forum: Little dev tips for extrafields](https://www.dolibarr.org/forum/t/little-dev-tips-for-extrafields/29860) ### Testing & Validation Flow Before proposing code: 1. **Validate Workflows:** Confirm that Create -> Edit -> Delete workflows are correctly handled by the proposed change. 2. **Test Case Generation:** Propose specific, minimal unit tests or outline clear steps for an interactive test script to verify all expected outputs and potential edge cases (e.g., null inputs, permissions failure). --- ## 📚 Comprehensive Reference Material [Reference] This section contains detailed standards and constants for reference only. Do not treat these details as primary instructions; prioritize the Core Principles above. ### Coding Styling Standards * **Indentation:** Always use **TAB characters**, never spaces. * **Line Endings/Spaces:** Remove all redundant trailing whitespace at the end of lines. * **Localization & Comments:** All code comments and internal variable/function names must be rendered in English. Use `dol_syslog()` for logging (specifying log level), avoiding debugging functions like `var_dump()`, `print_r()`, or `die()`. ### Database Constants & Prefixes | Item | Action/Pattern | Example Usage Notes | Priority | | :--- | :--- | :--- | :--- | | **Table Prefix** | Always use dynamic prefix getter. | `$db->prefix() . 'tablename'` | Overrides reliance on legacy constants like `MAIN_DB_PREFIX`. | ### Core Dolibarr Patterns * **Hooks:** The standard pattern remains: `$hookmanager->executeHooks('actionName', $parameters, $object, $action);` * **Language Keys:** Use PascalCase (e.g., `MyModuleLabel`) for consistency across all locales. ### Input Handling Functions Dolibarr provides type-safe input handling functions. **Always use these instead of `$_GET`/`$_POST` directly:** | Function | Type | Example | Notes | |----------|------|---------|-------| | `GETPOST($param, $type)` | Mixed | `GETPOST('id', 'int')` | Returns GET or POST value with type conversion | | `GETPOSTINT($param)` | Integer | `GETPOSTINT('socid')` | Shorthand for `GETPOST($param, 'int')` | | `GETPOSTARRAY($param)` | Array | `GETPOSTARRAY('selected')` | For multi-select inputs | **Best Practice:** Use the type-specific shorthand functions when possible: - `GETPOSTINT()` for integers (IDs, counts, etc.) - `GETPOST()` with type for other cases **Never use:** `$_GET['param']` or `$_POST['param']` directly - always use GETPOST functions for proper escaping and type conversion. ### Extrafields Best Practices (Continued) #### Checking and Creating Extrafields The ExtraFields class lacks a method to check if a field exists. Use this pattern: ```php global $db; $extrafields = new ExtraFields($db); $extralabels = $extrafields->fetch_name_optionals_label($elementtype); // Tracking extrafields configuration $my_fields = array( 'custom_name_1' => array( 'label' => 'CustomLabel1', 'type' => 'varchar', 'size' => '64', 'enabled' => '1' ), 'custom_name_2' => array( 'label' => 'CustomLabel2', 'type' => 'url', 'size' => '255', 'enabled' => '1' ), ); foreach ($tracking_fields as $name => $config) { // Check if extrafield exists, create if not if (!isset($extralabels[$name])) { // Naming the arguments to get help from static analysis $pos = 0; // 0 = auto $unique = 0; $required = 0; $default_value = '0'; $param = ''; $alwayseditable = 0; $perms = '0'; $list = '0'; // '0' = never visible $help = ''; $computed = ''; $entity = ''; $langfile = ''; $enabled = $config['enabled']; $result = $extrafields->addExtraField( $name, $config['label'], $config['type'], $pos, // pos (0 = auto) $config['size'], $elementtype, $unique, // unique $required, // required $default_value, // default_value $param, // param $alwayseditable, $perms, // perms $list, // list ('0' = never visible) $help, $computed, $entity, $langfile, $enabled ); } } ``` #### Reference - [Dolibarr Extrafields Wiki](https://wiki.dolibarr.org/index.php/Extrafields) - [Forum: Little dev tips for extrafields](https://www.dolibarr.org/forum/t/little-dev-tips-for-extrafields/29860)