Chapitre 3 : Mappers
Les mappers (classes dm*) transforment les objets Dolibarr en JSON optimisé pour React.
Pourquoi des mappers ?
Les objets Dolibarr ont des noms de champs souvent cryptiques (fk_soc, rowid, etc.). Les mappers :
- Renomment les champs (rowid -> id)
- Résolvent les clés étrangères (fk_soc -> objet société)
- Transforment les données (fichier -> base64)
- Filtrent les champs exposés
Structure de base
<?php
// smartmaker-api/Mappers/dmProduct.php
namespace MonModule\Api\Mappers;
class dmProduct extends \SmartAuth\DolibarrMapping\dmBase
{
use \SmartAuth\DolibarrMapping\dmTrait;
// Type d'objet
protected $type = "object";
// Classe Dolibarr source
protected $parentClassName = 'Product';
// Pour les extrafields
protected $parentClassToUseForExtraFields = "Product";
protected $parentElementToUseForExtraFields = "product";
protected $parentTableElementToUseForExtraFields = 'product';
// Mapping : clé Dolibarr => clé React
protected $listOfPublishedFields = [
'rowid' => 'id',
'ref' => 'ref',
'label' => 'label',
'description' => 'description',
'price' => 'price',
'price_ttc' => 'price_ttc',
'tva_tx' => 'vat_rate',
'stock_reel' => 'stock',
'tosell' => 'status',
'date_creation' => 'created_at',
'tms' => 'updated_at'
];
public function __construct()
{
global $langs;
$langs->load("products");
$this->boot();
}
}
Mapping des champs
Champs simples
protected $listOfPublishedFields = [
'rowid' => 'id', // Renommage
'nom' => 'name',
'address' => 'address', // Même nom
'email' => 'email'
];
Clés étrangères
Les clés étrangères (fk_*) sont automatiquement résolues :
protected $listOfPublishedFields = [
// Résolution automatique
'fk_soc' => 'thirdparty', // ID -> objet société
'fk_pays' => 'country', // ID -> nom du pays
'fk_user' => 'user', // ID -> objet utilisateur
'fk_project' => 'project' // ID -> objet projet
];
Extrafields
protected $listOfPublishedFields = [
// Champs standards
'rowid' => 'id',
'label' => 'label',
// Extrafields (préfixe options_)
'options_color' => 'color',
'options_size' => 'size',
'options_myfield' => 'my_custom_field'
];
Transformation de valeurs
Méthodes fieldFilterValueXXX
Pour transformer une valeur, créez une méthode fieldFilterValueXXX où XXX est le nom du champ :
/**
* Transformer une date
*/
public function fieldFilterValueDateCreation($object)
{
if (empty($object->date_creation)) {
return null;
}
return dol_print_date($object->date_creation, 'dayhour');
}
/**
* Formater un prix
*/
public function fieldFilterValuePrice($object)
{
return (float) $object->price;
}
/**
* Statut en texte
*/
public function fieldFilterValueStatus($object)
{
$statuses = [
0 => 'draft',
1 => 'validated',
2 => 'closed'
];
return $statuses[$object->status] ?? 'unknown';
}
Logo en base64
public function fieldFilterValueLogo($object)
{
global $conf;
$dir = $conf->societe->multidir_output[$object->entity];
$logoPath = $dir . "/" . $object->id . "/logos/" . $object->logo;
if (!file_exists($logoPath)) {
return null;
}
$type = pathinfo($logoPath, PATHINFO_EXTENSION);
$content = file_get_contents($logoPath);
return 'data:image/' . $type . ';base64,' . base64_encode($content);
}
Photos attachées
public function fieldFilterValuePhotos($object)
{
global $conf;
$photos = [];
$dir = $conf->product->dir_output . '/' . $object->ref;
if (!is_dir($dir)) {
return $photos;
}
$files = scandir($dir);
foreach ($files as $file) {
if (preg_match('/\.(jpg|jpeg|png|gif)$/i', $file)) {
$path = $dir . '/' . $file;
$type = pathinfo($path, PATHINFO_EXTENSION);
$content = file_get_contents($path);
$photos[] = [
'name' => $file,
'src' => 'data:image/' . $type . ';base64,' . base64_encode($content)
];
}
}
return $photos;
}
Contacts associés
public function fieldFilterValueContacts($object)
{
$contacts = $object->liste_contact(-1, 'external');
return array_map(function($c) {
return [
'id' => $c['id'],
'name' => $c['firstname'] . ' ' . $c['lastname'],
'email' => $c['email'],
'phone' => $c['phone']
];
}, $contacts);
}
Override de types
protected $parentFieldsOverride = [
'duree' => ['type' => 'duration', 'required' => 'required'],
'contacts' => ['type' => 'array'],
'status' => ['type' => 'select'],
'price' => ['type' => 'price']
];
Objets avec lignes
Pour les factures, commandes, etc. avec des lignes :
<?php
namespace MonModule\Api\Mappers;
class dmInvoice extends \SmartAuth\DolibarrMapping\dmBase
{
use \SmartAuth\DolibarrMapping\dmTrait;
protected $type = "object";
protected $parentClassName = 'Facture';
// Classe des lignes
protected $parentClassNameForLines = 'FactureLigne';
// Mapping des champs principaux
protected $listOfPublishedFields = [
'rowid' => 'id',
'ref' => 'ref',
'fk_soc' => 'thirdparty',
'total_ht' => 'total_ht',
'total_ttc' => 'total_ttc',
'fk_statut' => 'status',
'date' => 'date'
];
// Mapping des champs de lignes
protected $listOfPublishedFieldsForLines = [
'id' => 'id',
'desc' => 'description',
'qty' => 'quantity',
'subprice' => 'unit_price',
'total_ht' => 'total_ht',
'total_ttc' => 'total_ttc',
'tva_tx' => 'vat_rate'
];
// Description des champs de lignes (pour le formulaire côté React)
protected $parentFieldsForLines = [
'id' => ['type' => 'integer', 'label' => 'ID', 'visible' => -1],
'desc' => ['type' => 'html', 'label' => 'Description', 'visible' => 1],
'qty' => ['type' => 'integer', 'label' => 'Quantité', 'visible' => 1],
'subprice' => ['type' => 'price', 'label' => 'Prix unitaire', 'visible' => 1]
];
// Titre de la section lignes
protected $parentLabelForLines = "Lignes de facture";
public function __construct()
{
global $langs;
$langs->load("bills");
$this->boot();
}
}
Utilisation dans un controller
public function show($payload = null)
{
global $db;
$id = $payload['id'];
require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
$invoice = new \Facture($db);
$res = $invoice->fetch($id);
if ($res <= 0) {
return ['Invoice not found', 404];
}
// Charger les données additionnelles
$invoice->fetch_optionals(); // Extrafields
$invoice->fetch_lines(); // Lignes
// Mapper
$mapper = new dmInvoice();
$data = $mapper->exportMappedData($invoice);
return [$data, 200];
}
Extrafields dynamiques
public function __construct()
{
global $conf, $langs;
$langs->load("products");
// Ajouter les extrafields depuis la configuration
$extrafields = getDolGlobalString('MYMODULE_PRODUCT_EXTRAFIELDS');
if (!empty($extrafields)) {
foreach (explode(',', $extrafields) as $field) {
$key = "options_" . trim($field);
$this->listOfPublishedFields[$key] = trim($field);
}
}
$this->boot();
}
Exemple complet
<?php
// smartmaker-api/Mappers/dmThirdparty.php
namespace MonModule\Api\Mappers;
class dmThirdparty extends \SmartAuth\DolibarrMapping\dmBase
{
use \SmartAuth\DolibarrMapping\dmTrait;
protected $type = "object";
protected $parentClassName = 'Societe';
protected $parentClassToUseForExtraFields = "Societe";
protected $parentElementToUseForExtraFields = "societe";
protected $parentTableElementToUseForExtraFields = 'societe';
protected $listOfPublishedFields = [
'rowid' => 'id',
'nom' => 'name',
'name_alias' => 'alias',
'address' => 'address',
'zip' => 'zip',
'town' => 'city',
'fk_pays' => 'country',
'phone' => 'phone',
'email' => 'email',
'url' => 'website',
'siren' => 'siren',
'siret' => 'siret',
'tva_intra' => 'vat_number',
'logo' => 'logo',
'status' => 'status',
'date_creation' => 'created_at',
// Extrafields
'options_category' => 'category'
];
protected $parentFieldsOverride = [
'logo' => ['type' => 'image']
];
public function __construct()
{
global $langs;
$langs->load("companies");
$this->boot();
}
/**
* Logo en base64
*/
public function fieldFilterValueLogo($object)
{
global $conf;
if (empty($object->logo)) {
return null;
}
$dir = $conf->societe->multidir_output[$object->entity];
$path = $dir . "/" . $object->id . "/logos/" . $object->logo;
if (!file_exists($path)) {
return null;
}
$ext = pathinfo($path, PATHINFO_EXTENSION);
$content = file_get_contents($path);
return 'data:image/' . $ext . ';base64,' . base64_encode($content);
}
/**
* Statut en texte
*/
public function fieldFilterValueStatus($object)
{
return $object->status == 1 ? 'active' : 'inactive';
}
}
Points clés à retenir
- listOfPublishedFields définit le mapping clé Dolibarr -> clé React
- fieldFilterValueXXX transforme une valeur spécifique
- fetch_optionals() charge les extrafields
- fetch_lines() charge les lignes pour les objets composés
- Les clés étrangères (fk_*) sont résolues automatiquement