---
title: "Chapitre 2 : Controllers"
weight: 640
---

# Chapitre 2 : Controllers

Les controllers contiennent la logique métier de l'API.

## Structure d'un controller

```php
<?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
<?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

```php
/**
 * 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

```php
/**
 * 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

```php
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

```php
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

1. **Retourner [data, code]** : toujours un tableau avec données et code HTTP
2. **$payload** contient les paramètres d'URL et le body JSON
3. **Utiliser les mappers** pour convertir les objets Dolibarr
4. **Valider les entrées** avant traitement
5. **Vérifier les droits** pour les actions sensibles

[← Chapitre précédent](/training/module8-backend-api/routage) | [Retour au module](/training/module8-backend-api) | [Chapitre suivant : Mappers ->](/training/module8-backend-api/mappers)
