Chapter 3: Mappers
Mapper classes (dm*) transform Dolibarr objects into optimized JSON for React.
Why Mappers?
Dolibarr objects often have cryptic field names (fk_soc, rowid, etc.). Mappers:
- Rename fields (rowid -> id)
- Resolve foreign keys (fk_soc -> thirdparty object)
- Transform data (file -> base64)
- Filter exposed fields
Basic Structure
<?php
// smartmaker-api/Mappers/dmProduct.php
namespace MonModule\Api\Mappers;
class dmProduct extends \SmartAuth\DolibarrMapping\dmBase
{
use \SmartAuth\DolibarrMapping\dmTrait;
// Object type
protected $type = "object";
// Source Dolibarr class
protected $parentClassName = 'Product';
// For extrafields
protected $parentClassToUseForExtraFields = "Product";
protected $parentElementToUseForExtraFields = "product";
protected $parentTableElementToUseForExtraFields = 'product';
// Mapping: Dolibarr key => React key
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();
}
}
Field Mapping
Simple Fields
protected $listOfPublishedFields = [
'rowid' => 'id', // Rename
'nom' => 'name',
'address' => 'address', // Same name
'email' => 'email'
];
Foreign Keys
Foreign keys (fk_*) are automatically resolved:
protected $listOfPublishedFields = [
// Automatic resolution
'fk_soc' => 'thirdparty', // ID -> thirdparty object
'fk_pays' => 'country', // ID -> country name
'fk_user' => 'user', // ID -> user object
'fk_project' => 'project' // ID -> project object
];
Extrafields
protected $listOfPublishedFields = [
// Standard fields
'rowid' => 'id',
'label' => 'label',
// Extrafields (options_ prefix)
'options_color' => 'color',
'options_size' => 'size',
'options_myfield' => 'my_custom_field'
];
Value Transformation
fieldFilterValueXXX Methods
To transform a value, create a fieldFilterValueXXX method where XXX is the field name:
/**
* Transform a date
*/
public function fieldFilterValueDateCreation($object)
{
if (empty($object->date_creation)) {
return null;
}
return dol_print_date($object->date_creation, 'dayhour');
}
/**
* Format a price
*/
public function fieldFilterValuePrice($object)
{
return (float) $object->price;
}
/**
* Status as text
*/
public function fieldFilterValueStatus($object)
{
$statuses = [
0 => 'draft',
1 => 'validated',
2 => 'closed'
];
return $statuses[$object->status] ?? 'unknown';
}
Logo as 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);
}
Attached Photos
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;
}
Associated Contacts
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);
}
Type Override
protected $parentFieldsOverride = [
'duree' => ['type' => 'duration', 'required' => 'required'],
'contacts' => ['type' => 'array'],
'status' => ['type' => 'select'],
'price' => ['type' => 'price']
];
Objects with Lines
For invoices, orders, etc. with lines:
<?php
namespace MonModule\Api\Mappers;
class dmInvoice extends \SmartAuth\DolibarrMapping\dmBase
{
use \SmartAuth\DolibarrMapping\dmTrait;
protected $type = "object";
protected $parentClassName = 'Facture';
// Class for lines
protected $parentClassNameForLines = 'FactureLigne';
// Mapping of main fields
protected $listOfPublishedFields = [
'rowid' => 'id',
'ref' => 'ref',
'fk_soc' => 'thirdparty',
'total_ht' => 'total_ht',
'total_ttc' => 'total_ttc',
'fk_statut' => 'status',
'date' => 'date'
];
// Mapping of line fields
protected $listOfPublishedFieldsForLines = [
'id' => 'id',
'desc' => 'description',
'qty' => 'quantity',
'subprice' => 'unit_price',
'total_ht' => 'total_ht',
'total_ttc' => 'total_ttc',
'tva_tx' => 'vat_rate'
];
// Description of line fields (for React form)
protected $parentFieldsForLines = [
'id' => ['type' => 'integer', 'label' => 'ID', 'visible' => -1],
'desc' => ['type' => 'html', 'label' => 'Description', 'visible' => 1],
'qty' => ['type' => 'integer', 'label' => 'Quantity', 'visible' => 1],
'subprice' => ['type' => 'price', 'label' => 'Unit Price', 'visible' => 1]
];
// Section title for lines
protected $parentLabelForLines = "Invoice Lines";
public function __construct()
{
global $langs;
$langs->load("bills");
$this->boot();
}
}
Usage in a 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];
}
// Load additional data
$invoice->fetch_optionals(); // Extrafields
$invoice->fetch_lines(); // Lines
// Map
$mapper = new dmInvoice();
$data = $mapper->exportMappedData($invoice);
return [$data, 200];
}
Dynamic Extrafields
public function __construct()
{
global $conf, $langs;
$langs->load("products");
// Add extrafields from 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();
}
Complete Example
<?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 as 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);
}
/**
* Status as text
*/
public function fieldFilterValueStatus($object)
{
return $object->status == 1 ? 'active' : 'inactive';
}
}
Key Points to Remember
- listOfPublishedFields defines the Dolibarr key -> React key mapping
- fieldFilterValueXXX transforms a specific value
- fetch_optionals() loads extrafields
- fetch_lines() loads lines for compound objects
- Foreign keys (fk_*) are automatically resolved
Previous Chapter: Controllers | Back to Module | Next: Extrafields ->