---
source_hash: "ae7bd00e"
title: "Chapter 3: Mappers"
weight: 650
---

# 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
<?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

```php
protected $listOfPublishedFields = [
    'rowid'     => 'id',           // Rename
    'nom'       => 'name',
    'address'   => 'address',      // Same name
    'email'     => 'email'
];
```

### Foreign Keys

Foreign keys (fk_*) are automatically resolved:

```php
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

```php
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:

```php
/**
 * 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

```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);
}
```

### Attached Photos

```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;
}
```

### Associated Contacts

```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);
}
```

## Type Override

```php
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
<?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

```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];
    }

    // Load additional data
    $invoice->fetch_optionals();  // Extrafields
    $invoice->fetch_lines();      // Lines

    // Map
    $mapper = new dmInvoice();
    $data = $mapper->exportMappedData($invoice);

    return [$data, 200];
}
```

## Dynamic Extrafields

```php
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
<?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

1. **listOfPublishedFields** defines the Dolibarr key -> React key mapping
2. **fieldFilterValueXXX** transforms a specific value
3. **fetch_optionals()** loads extrafields
4. **fetch_lines()** loads lines for compound objects
5. Foreign keys (fk_*) are automatically resolved

[Previous Chapter: Controllers](/training/module8-backend-api/controllers) | [Back to Module](/training/module8-backend-api) | [Next: Extrafields ->](/training/module8-backend-api/extrafields)
