Chapter 2: Controllers
Controllers contain the business logic of the API.
Controller Structure
<?php
// smartmaker-api/Controllers/ItemController.php
namespace MonModule\Api;
class ItemController
{
public function __construct() {}
/**
* Item list
* @param array|null $payload
* @return array [data, httpCode]
*/
public function index($payload = null)
{
global $db, $user;
// Logic here
return [['items' => $items], 200];
}
}
Available Global Variables
In protected routes:
$db: Dolibarr database connection$user: logged Dolibarr user (via JWT)$conf: Dolibarr configuration$langs: translations
Complete CRUD
<?php
namespace MonModule\Api;
use MonModule\Api\Mappers\dmProduct;
class ProductController
{
/**
* Product list
*/
public function index($payload = null)
{
global $db, $user;
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
// Pagination parameters
$limit = $payload['limit'] ?? 50;
$offset = $payload['offset'] ?? 0;
// SQL query
$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];
}
/**
* Product detail
*/
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];
}
// Load extrafields
$product->fetch_optionals();
$mapper = new dmProduct();
$data = $mapper->exportMappedData($product);
return [$data, 200];
}
/**
* Create a product
*/
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;
// Create in database
$res = $product->create($user);
if ($res < 0) {
return ['Error creating product: ' . $product->error, 500];
}
// Save extrafields
if (!empty($payload['extrafields'])) {
foreach ($payload['extrafields'] as $key => $value) {
$product->array_options['options_' . $key] = $value;
}
$product->insertExtraFields();
}
return [['id' => $res], 201];
}
/**
* Update a product
*/
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];
}
// Update provided fields
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];
}
// Update 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];
}
/**
* Delete a product
*/
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];
}
// Check permissions
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];
}
}
Search with Filters
/**
* Advanced search
*/
public function search($payload = null)
{
global $db;
require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
// Filters
$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;
// Build query
$sql = "SELECT p.rowid FROM " . MAIN_DB_PREFIX . "product as p";
// Join category if needed
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') . ")";
// Filters
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];
}
File Management
/**
* Download a file
*/
public function download($payload = null)
{
global $conf, $db;
$element = $payload['element']; // e.g., 'product'
$parentId = $payload['parentId'];
$ref = $payload['ref']; // file name
// Build path
$dir = $conf->$element->dir_output;
$filepath = $dir . '/' . $parentId . '/' . $ref;
if (!file_exists($filepath)) {
return ['File not found', 404];
}
// Return file as 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];
}
/**
* Upload a file
*/
public function upload($payload = null)
{
global $conf, $user;
$element = $payload['element'];
$parentId = $payload['parentId'];
$filename = $payload['filename'];
$content = $payload['content']; // base64
// Decode content
$data = base64_decode(preg_replace('#^data:.+;base64,#', '', $content));
// Destination path
$dir = $conf->$element->dir_output . '/' . $parentId;
// Create directory if needed
if (!is_dir($dir)) {
dol_mkdir($dir);
}
$filepath = $dir . '/' . $filename;
// Save
if (file_put_contents($filepath, $data) === false) {
return ['Error saving file', 500];
}
return [['path' => $filepath], 201];
}
Permission Checking
public function delete($payload = null)
{
global $db, $user;
// Check specific permission
if (!$user->hasRight('monmodule', 'delete')) {
return ['Permission denied', 403];
}
// Check if admin
if (!$user->admin) {
return ['Admin required', 403];
}
// Rest of code...
}
Logging
public function create($payload = null)
{
global $db, $user;
// Debug log
dol_syslog("ProductController::create by " . $user->login, LOG_DEBUG);
// On error
dol_syslog("ProductController::create error: " . $product->error, LOG_ERR);
// ...
}
Key Points to Remember
- Return [data, code]: always an array with data and HTTP code
- $payload contains URL parameters and JSON body
- Use mappers to convert Dolibarr objects
- Validate inputs before processing
- Check permissions for sensitive actions
Previous Chapter | Back to Module | Next Chapter: Mappers ->