Table of Contents

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 :

Structure de base

snippet.php
<?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

snippet.php
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 :

snippet.php
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

snippet.php
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 :

snippet.php
/**
 * 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

snippet.php
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

snippet.php
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

snippet.php
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

snippet.php
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 :

snippet.php
<?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

snippet.php
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

snippet.php
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

snippet.php
<?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

  1. listOfPublishedFields définit le mapping clé Dolibarr → clé React
  2. fieldFilterValueXXX transforme une valeur spécifique
  3. fetchoptionals() charge les extrafields 4. fetchlines()** charge les lignes pour les objets composés 5. Les clés étrangères (fk*) sont résolues automatiquement ← Controllers | Retour au module | Extrafields →