SmarMaker - Documentation
Docs» 15_training:module8-backend-api:mappers

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

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 →
Previous Next

Made with ❤ by CAP-REL · SmartMaker · GNU AGPL v3+
Code source · Faire un don
SmarMaker - Documentation
Traductions de cette page:
  • Français
  • Deutsch
  • English
  • Español
  • Italiano
  • Nederlands

Table of Contents

Table des matières

  • Chapitre 3 : Mappers
    • Pourquoi des mappers ?
    • Structure de base
    • Mapping des champs
      • Champs simples
      • Clés étrangères
      • Extrafields
    • Transformation de valeurs
      • Méthodes fieldFilterValueXXX
      • Logo en base64
      • Photos attachées
      • Contacts associés
    • Override de types
    • Objets avec lignes
    • Utilisation dans un controller
    • Extrafields dynamiques
    • Exemple complet
    • Points clés à retenir
  • SmartAuth
  • SmartMaker - Back (PHP)
    • Mapping Dolibarr - React
  • SmartMaker - Front (React)
    • Animations de pages
    • Architecture
    • Astuces
    • Calendar
    • Composants et pages
    • Configuration du Provider
    • Debug et Logs
    • Hooks SmartCommon
    • PWA (Progressive Web App)
    • Requêtes API
    • Routage
    • SmartCommon
    • Stockage de données
    • Thèmes
    • Traductions
  • HowTo - Pas à pas - Votre première application
    • Développement PHP (back)
    • Développement React (front)
    • Première étape : Module Builder Dolibarr
    • SmartAuth
    • SmartBoot : Un "squelette" quasiment prêt à l'emploi
  • Formation SmartMaker
    • Module 1 : Fondamentaux JavaScript ES6+
      • Chapitre 1 : Variables et Scope
      • Chapitre 2 : Fonctions
      • Chapitre 3 : Programmation Asynchrone
      • Chapitre 4 : Modules ES6
    • Module 2 : Introduction à React
      • Chapitre 1 : Philosophie React
      • Chapitre 2 : JSX
      • Chapitre 3 : Composants
    • Module 3 : Hooks React Fondamentaux
      • Chapitre 1 : useState
      • Chapitre 2 : useEffect
      • Chapitre 3 : useRef
      • Chapitre 4 : useContext
    • Module 4 : React Avancé
      • Chapitre 1 : useCallback et useMemo
      • Chapitre 2 : Custom Hooks
      • Chapitre 3 : Redux et Redux Toolkit
    • Module 5 : Architecture SmartMaker
      • Chapitre 1 : Structure du projet
      • Chapitre 2 : Configuration
      • Chapitre 3 : Flux de données
    • Module 6 : SmartCommon - Composants
      • Chapitre 1 : Mise en page
      • Chapitre 2 : Navigation
      • Chapitre 3 : Formulaires
      • Chapitre 4 : Affichage
    • Module 7 : SmartCommon - Hooks
      • Chapitre 1 : useApi
      • Chapitre 2 : Gestion d'état
      • Chapitre 3 : Hooks utilitaires
    • Module 8 : Backend API (PHP)
      • Chapitre 1 : Routage
      • Chapitre 2 : Controllers
      • Chapitre 3 : Mappers
      • Extrafields et formulaires dynamiques
    • Module 9 : Intégration complète
      • Chapitre 1 : Backend
      • Chapitre 2 : Frontend
      • Chapitre 3 : Déploiement
    • Module 10 : Fonctionnalités avancées
      • Chapitre 1 : Mode offline
      • Chapitre 2 : Internationalisation (i18n)
      • Chapitre 3 : Autres fonctionnalités
    • Module 11 : Bonnes pratiques
  • Démonstration
  • Start
  • Composants et pages
  • Afficher le texte source
  • Anciennes révisions
  • Liens de retour
  • Haut de page