SmartMaker - Back (PHP)

The back-office part of SmartMaker fits into a standard Dolibarr module.

Folder structure

When you deploy SmartMaker in your Dolibarr module, the following folders are created:

Folder Description
mobile/ React source code of the mobile application
pwa/ Compiled application + api.php entry point
smartmaker-api/ PHP controllers and mappers

Plus a smartmaker-api-prepend.php file that factors out the includes.

The PHP router

Syntax

Route::action(path, Controller::class, method, protected);
Parameter Description
action get, post, put, delete
path API path (e.g. items, items/{id})
Controller::class PHP class to call
method Method of the class
protected true = authenticated route, false = public

Complete example (api.php)

<?php
require_once '../smartmaker-api-prepend.php';

use SmartAuth\Api\AuthController;
use SmartAuth\Api\RouteController as Route;
use MyModule\Api\ItemController;

// Public routes (authentication)
Route::get('login',     AuthController::class, 'index');
Route::post('login',    AuthController::class, 'login');
Route::get('refresh',   AuthController::class, 'refresh');

// Protected routes (require a JWT token)
Route::post('logout',   AuthController::class, 'logout', true);
Route::post('device',   AuthController::class, 'device', true);

// CRUD on the items
Route::get('items',         ItemController::class, 'index', true);
Route::get('items/{id}',    ItemController::class, 'show', true);
Route::post('items',        ItemController::class, 'create', true);
Route::put('items/{id}',    ItemController::class, 'update', true);
Route::delete('items/{id}', ItemController::class, 'delete', true);

// Routes with filters
Route::post('items/{filter}', ItemController::class, 'search', true);

// Files
Route::get('file/{element}/{parentid}/{ref}', FileController::class, 'download', true);

// Fallback
json_reply('Access denied', 403);

Dynamic parameters

Parameters between braces are passed in the payload:

// Route: GET /items/123
Route::get('items/{id}', ItemController::class, 'show', true);

// In the controller:
public function show($payload)
{
    $id = $payload['id']; // 123
}

Creating a Controller

Basic structure

<?php
namespace MyModule\Api;

class ItemController
{
    public function __construct() {}

    /**
     * Item list
     * @param array|null $payload Request data
     * @return array [data, httpCode]
     */
    public function index($payload = null)
    {
        global $db, $user;

        // Your logic here
        $items = [];

        return [$items, 200];
    }

    /**
     * Fetch one item
     */
    public function show($payload = null)
    {
        global $db;

        $id = $payload['id'] ?? null;

        if ($id === null) {
            return ['Not Found', 404];
        }

        // Fetch the object
        $item = new \MyObject($db);
        $res = $item->fetch($id);

        if ($res <= 0) {
            return ['Not Found', 404];
        }

        // Map for the front
        $mapping = new dmMyObject();
        $data = $mapping->exportMappedData($item);

        return [$data, 200];
    }

    /**
     * Create an item
     */
    public function create($payload = null)
    {
        global $db, $user;

        $item = new \MyObject($db);
        $item->label = $payload['label'];
        $item->description = $payload['description'];

        $res = $item->create($user);

        if ($res < 0) {
            return ['Error creating item', 500];
        }

        return [['id' => $res], 201];
    }

    /**
     * Update an item
     */
    public function update($payload = null)
    {
        global $db, $user;

        $id = $payload['id'] ?? null;

        $item = new \MyObject($db);
        $res = $item->fetch($id);

        if ($res <= 0) {
            return ['Not Found', 404];
        }

        // Update the fields
        if (isset($payload['label'])) {
            $item->label = $payload['label'];
        }

        $res = $item->update($user);

        if ($res < 0) {
            return ['Error updating item', 500];
        }

        return ['Updated', 200];
    }

    /**
     * Delete an item
     */
    public function delete($payload = null)
    {
        global $db, $user;

        $id = $payload['id'] ?? null;

        $item = new \MyObject($db);
        $res = $item->fetch($id);

        if ($res <= 0) {
            return ['Not Found', 404];
        }

        $res = $item->delete($user);

        if ($res < 0) {
            return ['Error deleting item', 500];
        }

        return ['Deleted', 200];
    }

    /**
     * Search with filters
     */
    public function search($payload = null)
    {
        global $db;

        $filter = $payload['filter'] ?? 'all';
        $limit = $payload['limit'] ?? 10;

        // Build the SQL query
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "myobject";
        $sql .= " WHERE 1=1";

        if ($filter === 'active') {
            $sql .= " AND status = 1";
        }

        $sql .= " LIMIT " . (int) $limit;

        $resql = $db->query($sql);
        $items = [];

        while ($obj = $db->fetch_object($resql)) {
            $item = new \MyObject($db);
            $item->fetch($obj->rowid);

            $mapping = new dmMyObject();
            $items[] = $mapping->exportMappedData($item);
        }

        return [$items, 200];
    }
}

Accessing the logged-in user

The JWT user is available in the payload:

public function update($payload = null)
{
    $user = $payload['user']; // Dolibarr User object

    dol_syslog("Action by: " . $user->login);
}

See also