Développement PHP (back)
SmartBoot ajoute à votre module Dolibarr toute la pile technique pour exposer une API REST.
Structure des fichiers
Après SmartBoot, votre module contient :
monmodule/
├── smartmaker-api-prepend.php # Fichier d'initialisation
├── pwa/
│ ├── api.php # Routeur API
│ └── .htaccess # Redirection Apache
└── smartmaker-api/
└── Controllers/ # Vos contrôleurs
smartmaker-api-prepend.php
Ce fichier initialise l'environnement SmartMaker :
- Entêtes Dolibarr obligatoires
- Chargement de l'autoloader SmartAuth
- Initialisation de la couche JWT
- Autoloader des classes de votre module
<note important>Ne modifiez pas ce fichier sauf si vous savez ce que vous faites.</note>
pwa/api.php - Le routeur
Syntaxe des routes
Route::action(path, Controller::class, method, protected);
| Paramètre | Description |
|---|---|
action | get, post, put, delete |
path | Chemin de l'API (ex: items, items/{id}) |
Controller::class | Classe PHP à appeler |
method | Méthode de la classe |
protected | true = authentification requise |
Exemple complet
<?php
require_once '../smartmaker-api-prepend.php';
use SmartAuth\Api\AuthController;
use SmartAuth\Api\RouteController as Route;
use MonModule\Api\ItemController;
use MonModule\Api\FileController;
// === Routes d'authentification ===
Route::get('login', AuthController::class, 'index'); // Infos connexion
Route::post('login', AuthController::class, 'login'); // Login
Route::get('refresh', AuthController::class, 'refresh'); // Refresh token
Route::post('logout', AuthController::class, 'logout', true);// Logout
Route::post('device', AuthController::class, 'device', true);// Enregistrer device
// === Routes métier ===
// CRUD Items
Route::get('items', ItemController::class, 'index', true);
Route::get('items/{id}', ItemController::class, 'show', true);
Route::post('items', ItemController::class, 'create', true);
Route::put('items/{id}', ItemController::class, 'update', true);
Route::delete('items/{id}', ItemController::class, 'delete', true);
// Recherche avec filtres
Route::post('items/search/{filter}', ItemController::class, 'search', true);
// Fichiers
Route::get('file/{element}/{id}/{filename}', FileController::class, 'download', true);
// Fallback - Accès refusé
json_reply('Access denied', 403);
Paramètres dynamiques
Les segments entre accolades sont passés dans le payload :
// Route: GET /items/123
Route::get('items/{id}', ItemController::class, 'show', true);
// Dans le controller:
public function show($payload)
{
$id = $payload['id']; // 123
}
pwa/.htaccess
Redirige toutes les requêtes vers api.php (Apache >= 2.2.16) :
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ api.php [QSA,L]
<note>Pour nginx, une configuration équivalente est nécessaire dans le fichier de configuration du serveur.</note>
Créer un Controller
Structure de base
<?php
// smartmaker-api/Controllers/ItemController.php
namespace MonModule\Api;
class ItemController
{
/**
* Liste des items
* GET /items
*/
public function index($payload = null)
{
global $db, $user;
$items = [];
$sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "monobject";
$sql .= " WHERE entity = " . $user->entity;
$sql .= " ORDER BY date_creation DESC";
$resql = $db->query($sql);
while ($obj = $db->fetch_object($resql)) {
$item = new \MonObject($db);
$item->fetch($obj->rowid);
$mapping = new dmMonObject();
$items[] = $mapping->exportMappedData($item);
}
return [$items, 200];
}
/**
* Détail d'un item
* GET /items/{id}
*/
public function show($payload = null)
{
global $db;
$id = $payload['id'] ?? null;
if (!$id) {
return ['ID required', 400];
}
$item = new \MonObject($db);
$res = $item->fetch($id);
if ($res <= 0) {
return ['Not found', 404];
}
$item->fetch_optionals();
$item->fetch_lines();
$mapping = new dmMonObject();
$data = $mapping->exportMappedData($item);
return [$data, 200];
}
/**
* Créer un item
* POST /items
*/
public function create($payload = null)
{
global $db, $user;
$item = new \MonObject($db);
$item->label = $payload['label'] ?? '';
$item->description = $payload['description'] ?? '';
$item->fk_user = $user->id;
$res = $item->create($user);
if ($res < 0) {
return [$item->error ?: 'Creation failed', 500];
}
return [['id' => $res], 201];
}
/**
* Modifier un item
* PUT /items/{id}
*/
public function update($payload = null)
{
global $db, $user;
$id = $payload['id'] ?? null;
$item = new \MonObject($db);
$res = $item->fetch($id);
if ($res <= 0) {
return ['Not found', 404];
}
// Mettre à jour les champs fournis
if (isset($payload['label'])) {
$item->label = $payload['label'];
}
if (isset($payload['description'])) {
$item->description = $payload['description'];
}
$res = $item->update($user);
if ($res < 0) {
return [$item->error ?: 'Update failed', 500];
}
return ['Updated', 200];
}
/**
* Supprimer un item
* DELETE /items/{id}
*/
public function delete($payload = null)
{
global $db, $user;
$id = $payload['id'] ?? null;
$item = new \MonObject($db);
$res = $item->fetch($id);
if ($res <= 0) {
return ['Not found', 404];
}
$res = $item->delete($user);
if ($res < 0) {
return [$item->error ?: 'Delete failed', 500];
}
return ['Deleted', 200];
}
/**
* Recherche avec filtres
* POST /items/search/{filter}
*/
public function search($payload = null)
{
global $db, $user;
$filter = $payload['filter'] ?? 'all';
$limit = $payload['limit'] ?? 20;
$offset = $payload['offset'] ?? 0;
$sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "monobject";
$sql .= " WHERE entity = " . $user->entity;
// Appliquer les filtres
switch ($filter) {
case 'active':
$sql .= " AND status = 1";
break;
case 'draft':
$sql .= " AND status = 0";
break;
case 'mine':
$sql .= " AND fk_user = " . $user->id;
break;
}
// Recherche texte
if (!empty($payload['search'])) {
$search = $db->escape($payload['search']);
$sql .= " AND (label LIKE '%$search%' OR description LIKE '%$search%')";
}
$sql .= " ORDER BY date_creation DESC";
$sql .= " LIMIT " . (int)$limit . " OFFSET " . (int)$offset;
$resql = $db->query($sql);
$items = [];
while ($obj = $db->fetch_object($resql)) {
$item = new \MonObject($db);
$item->fetch($obj->rowid);
$mapping = new dmMonObject();
$items[] = $mapping->exportMappedData($item);
}
return [$items, 200];
}
}
Accès à l'utilisateur connecté
L'utilisateur JWT est disponible via $payload['user'] ou la variable globale $user :
public function create($payload = null)
{
global $user;
// $user est l'objet User Dolibarr complet
dol_syslog("Action par: " . $user->login);
// Récupérer l'ID
$userId = $user->id;
// Vérifier les droits
if (!$user->rights->monmodule->write) {
return ['Permission denied', 403];
}
}
Créer une classe de mapping (dm*)
Les classes dm* transforment les objets Dolibarr en JSON pour React :
<?php
// smartmaker-api/dmMonObject.php
namespace MonModule\Api;
class dmMonObject extends \SmartAuth\DolibarrMapping\dmBase
{
use \SmartAuth\DolibarrMapping\dmTrait;
protected $type = "object";
protected $parentClassName = 'MonObject';
// Pour les extrafields
protected $parentClassToUseForExtraFields = "MonObject";
protected $parentElementToUseForExtraFields = "monobject";
// Mapping: champ Dolibarr => champ JSON
protected $listOfPublishedFields = [
'rowid' => 'id',
'ref' => 'ref',
'label' => 'label',
'description' => 'description',
'fk_soc' => 'thirdparty', // Résolu automatiquement
'fk_statut' => 'status',
'date_creation' => 'created_at',
// Extrafields
'options_myfield' => 'my_field',
];
public function __construct()
{
global $langs;
$langs->load("monmodule@monmodule");
$this->boot();
}
/**
* Transformer une valeur avant l'envoi
* Méthode magique: fieldFilterValue + NomDuChamp
*/
public function fieldFilterValueCreatedAt($object)
{
return dol_print_date($object->date_creation, 'dayhour');
}
}
Voir aussi
- Back (PHP) - Documentation complète des routes
- Mapping Dolibarr - Classes dm* en détail
- SmartAuth - Authentification JWT
- Développement React - Front-end