Chapitre 2 : Controllers
Les controllers contiennent la logique métier de l'API.
Structure d'un controller
<?php
// smartmaker-api/Controllers/ItemController.php
namespace MonModule\Api;
class ItemController
{
public function __construct() {}
/**
* Liste des items
* @param array|null $payload
* @return array [data, httpCode]
*/
public function index($payload = null)
{
global $db, $user;
// Logique ici
return [['items' => $items], 200];
}
}
Variables globales disponibles
Dans les routes protégées :
$db: connexion base de données Dolibarr$user: utilisateur Dolibarr connecté (via JWT)$conf: configuration Dolibarr$langs: traductions
CRUD complet
<?php
namespace MonModule\Api;
use MonModule\Api\Mappers\dmProduct;
class ProductController
{
/**
* Liste des produits
*/
public function index($payload = null)
{
global $db, $user;
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
// Paramètres de pagination
$limit = $payload['limit'] ?? 50;
$offset = $payload['offset'] ?? 0;
// Requête SQL
$sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "product";
$sql .= " WHERE entity IN (" . getEntity('product') . ")";
$sql .= " ORDER BY label ASC";
$sql .= " LIMIT " . (int) $limit;
$sql .= " OFFSET " . (int) $offset;
$resql = $db->query($sql);
$products = [];
if ($resql) {
$mapper = new dmProduct();
while ($obj = $db->fetch_object($resql)) {
$product = new \Product($db);
$product->fetch($obj->rowid);
$products[] = $mapper->exportMappedData($product);
}
}
return [['products' => $products], 200];
}
/**
* Détail d'un produit
*/
public function show($payload = null)
{
global $db;
$id = $payload['id'] ?? null;
if (!$id) {
return ['ID required', 400];
}
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
$product = new \Product($db);
$res = $product->fetch($id);
if ($res <= 0) {
return ['Product not found', 404];
}
// Charger les extrafields
$product->fetch_optionals();
$mapper = new dmProduct();
$data = $mapper->exportMappedData($product);
return [$data, 200];
}
/**
* Créer un produit
*/
public function create($payload = null)
{
global $db, $user;
// Validation
if (empty($payload['label'])) {
return ['Label required', 400];
}
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
$product = new \Product($db);
$product->ref = $payload['ref'] ?? '';
$product->label = $payload['label'];
$product->description = $payload['description'] ?? '';
$product->price = $payload['price'] ?? 0;
$product->status = $payload['status'] ?? 1;
// Créer en base
$res = $product->create($user);
if ($res < 0) {
return ['Error creating product: ' . $product->error, 500];
}
// Sauvegarder les extrafields
if (!empty($payload['extrafields'])) {
foreach ($payload['extrafields'] as $key => $value) {
$product->array_options['options_' . $key] = $value;
}
$product->insertExtraFields();
}
return [['id' => $res], 201];
}
/**
* Mettre à jour un produit
*/
public function update($payload = null)
{
global $db, $user;
$id = $payload['id'] ?? null;
if (!$id) {
return ['ID required', 400];
}
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
$product = new \Product($db);
$res = $product->fetch($id);
if ($res <= 0) {
return ['Product not found', 404];
}
// Mettre à jour les champs fournis
if (isset($payload['label'])) {
$product->label = $payload['label'];
}
if (isset($payload['description'])) {
$product->description = $payload['description'];
}
if (isset($payload['price'])) {
$product->price = $payload['price'];
}
if (isset($payload['status'])) {
$product->status = $payload['status'];
}
$res = $product->update($product->id, $user);
if ($res < 0) {
return ['Error updating product: ' . $product->error, 500];
}
// Mettre à jour les extrafields
if (!empty($payload['extrafields'])) {
$product->fetch_optionals();
foreach ($payload['extrafields'] as $key => $value) {
$product->array_options['options_' . $key] = $value;
}
$product->updateExtraFields();
}
return ['Updated', 200];
}
/**
* Supprimer un produit
*/
public function delete($payload = null)
{
global $db, $user;
$id = $payload['id'] ?? null;
if (!$id) {
return ['ID required', 400];
}
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
$product = new \Product($db);
$res = $product->fetch($id);
if ($res <= 0) {
return ['Product not found', 404];
}
// Vérifier les droits
if (!$user->hasRight('produit', 'supprimer')) {
return ['Permission denied', 403];
}
$res = $product->delete($user);
if ($res < 0) {
return ['Error deleting product: ' . $product->error, 500];
}
return ['Deleted', 200];
}
}
Recherche avec filtres
/**
* Recherche avancée
*/
public function search($payload = null)
{
global $db;
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
// Filtres
$search = $payload['search'] ?? '';
$category = $payload['category'] ?? null;
$status = $payload['status'] ?? null;
$minPrice = $payload['minPrice'] ?? null;
$maxPrice = $payload['maxPrice'] ?? null;
// Pagination
$limit = min($payload['limit'] ?? 50, 100); // Max 100
$offset = $payload['offset'] ?? 0;
// Construction de la requête
$sql = "SELECT p.rowid FROM " . MAIN_DB_PREFIX . "product as p";
// Jointure catégorie si nécessaire
if ($category) {
$sql .= " INNER JOIN " . MAIN_DB_PREFIX . "categorie_product as cp";
$sql .= " ON p.rowid = cp.fk_product";
}
$sql .= " WHERE p.entity IN (" . getEntity('product') . ")";
// Filtres
if ($search) {
$sql .= " AND (p.label LIKE '%" . $db->escape($search) . "%'";
$sql .= " OR p.ref LIKE '%" . $db->escape($search) . "%')";
}
if ($category) {
$sql .= " AND cp.fk_categorie = " . (int) $category;
}
if ($status !== null) {
$sql .= " AND p.tosell = " . (int) $status;
}
if ($minPrice !== null) {
$sql .= " AND p.price >= " . (float) $minPrice;
}
if ($maxPrice !== null) {
$sql .= " AND p.price <= " . (float) $maxPrice;
}
$sql .= " ORDER BY p.label ASC";
$sql .= " LIMIT " . (int) $limit;
$sql .= " OFFSET " . (int) $offset;
$resql = $db->query($sql);
$products = [];
$mapper = new dmProduct();
while ($obj = $db->fetch_object($resql)) {
$product = new \Product($db);
$product->fetch($obj->rowid);
$products[] = $mapper->exportMappedData($product);
}
return [['products' => $products], 200];
}
Gestion des fichiers
/**
* Télécharger un fichier
*/
public function download($payload = null)
{
global $conf, $db;
$element = $payload['element']; // ex: 'product'
$parentId = $payload['parentId'];
$ref = $payload['ref']; // nom du fichier
// Construire le chemin
$dir = $conf->$element->dir_output;
$filepath = $dir . '/' . $parentId . '/' . $ref;
if (!file_exists($filepath)) {
return ['File not found', 404];
}
// Retourner le fichier en base64
$content = file_get_contents($filepath);
$mime = mime_content_type($filepath);
$base64 = base64_encode($content);
return [[
'filename' => $ref,
'mime' => $mime,
'content' => 'data:' . $mime . ';base64,' . $base64
], 200];
}
/**
* Uploader un fichier
*/
public function upload($payload = null)
{
global $conf, $user;
$element = $payload['element'];
$parentId = $payload['parentId'];
$filename = $payload['filename'];
$content = $payload['content']; // base64
// Décoder le contenu
$data = base64_decode(preg_replace('#^data:.+;base64,#', '', $content));
// Chemin de destination
$dir = $conf->$element->dir_output . '/' . $parentId;
// Créer le dossier si nécessaire
if (!is_dir($dir)) {
dol_mkdir($dir);
}
$filepath = $dir . '/' . $filename;
// Sauvegarder
if (file_put_contents($filepath, $data) === false) {
return ['Error saving file', 500];
}
return [['path' => $filepath], 201];
}
Vérification des droits
public function delete($payload = null)
{
global $db, $user;
// Vérifier un droit spécifique
if (!$user->hasRight('monmodule', 'delete')) {
return ['Permission denied', 403];
}
// Vérifier si admin
if (!$user->admin) {
return ['Admin required', 403];
}
// Suite du code...
}
Logging
public function create($payload = null)
{
global $db, $user;
// Log pour debug
dol_syslog("ProductController::create by " . $user->login, LOG_DEBUG);
// En cas d'erreur
dol_syslog("ProductController::create error: " . $product->error, LOG_ERR);
// ...
}
Points clés à retenir
- Retourner [data, code] : toujours un tableau avec données et code HTTP
- $payload contient les paramètres d'URL et le body JSON
- Utiliser les mappers pour convertir les objets Dolibarr
- Valider les entrées avant traitement
- Vérifier les droits pour les actions sensibles
← Chapitre précédent | Retour au module | Chapitre suivant : Mappers ->