Modella

Modella! è un sistema di prototipizzazione che, a partire da uno schema dati SQL, permette di generare il codice necessario alla sua gestione all’interno di un sistema Web. Con questo sistema di prototipizzazione, che consente di produrre codice con alti standard di sicurezza riducendo i tempi di realizzazione del software, sono stati realizzati diversi sistemi, fra cui:

Nella versione attuale, Modella! richiede la definizione dei campi dell’entità da gestire, ma dalla prossima versione sarà il sistema a leggere la tabella direttamente dal DB.

Interfaccia utente di Modella

L’output del sistema per la gestione di un’ipotetica classe utente comprende i file PHP:

utente.inc dichiarazioni per la classe
utente.api.php funzioni di interfaccia per la generazione delle pagine
utente.db.php funzioni di interfaccia DB
utente.delete.php script di cancellazione del record
utente.elenco.php pagina con elenco dei record
utente.form.php maschera di modifica del record
utente.insert.php script di inserimento del record
utente.new.php maschera di inserimento del record
utente.update.php script per la modifica del record

e gli script SQL per la generazione delle entità del DB:

t_utente.sql tabella
f_utente_delete.sql stored-procedure di eliminazione del record
f_utente_insert.sql stored-procedure di inserimento del record
f_utente_update.sql stored-procedure di modifica del record
v_utente_elenco.sql vista per la pagina di elenco record
v_utente_load.sql vista per le pagine di modifica dati

Tutti gli script sono generati a partire da dei modelli predefiniti nei quali il sistema inserisce, di volta in volta, i valori opportuni per i dati dell’entità da gestire.

Flusso del programma per sistemi di back-end

Lo schema di funzionamento per un’interfaccia gestionale, che permetta le operazioni di inserimento, modifica e cancellazione delle entità, è:

Inserimento nuova entità

utente.elenco.php mostra l’elenco (inizialmente vuoto) delle entità e un link alla pagina di inserimento nuovo utente
utente.new.php mostra la maschera di inserimento nuova entità
utente.insert.php richiama la stored-procedure di inserimento nuova entità
utente.form.php mostra la maschera di modifica entità

Modifica dati entità

utente.elenco.php mostra l’elenco delle entità con link alle maschere di modifica dati
utente.form.php mostra la maschera di modifica dati entità
utente.update.php richiama la stored-procedure di aggiornamento dei dati dell’entità
utente.form.php torna alla maschera di modifica entità

Cancellazione entità

utente.elenco.php mostra l’elenco delle entità con link alle maschere di modifica dati
utente.form.php mostra la maschera di modifica dati entità
utente.delete.php richiama la stored-procedure per la cancellazione dell’entità
utente.elenco.php torna all’elenco entità

Flusso del programma per sistemi di front-end

Lo schema di funzionamento per un’interfaccia in sola lettura dei dati può essere un sotto-insieme delle funzioni del sistema di gestione, tipicamente un elenco delle entità e una scheda con i dati in sola lettura.

Applicazioni pratiche

Questo paradigma è stato applicato con buon successo a siti di natura differente:

Interfaccia di amministrazione del sito Globalist

Interfaccia di amministrazione del sito Globalist

Interfaccia pubblica del sito iVid

Interfaccia pubblica del sito iVid

Caratteristiche del sistema

Ciascuna funzione è svolta da uno script specifico, in modo da rendere più rapido il debug del codice.

Le funzioni di interfaccia sono slegate dalle funzioni di gestione della base-dati che, al momento, possono gestire sia mySQL che PostgreSQL e possono venire estese ad altri RDBMS.

Queste sono le funzioni PHP di cancellazione e lettura di un record:

function utente_delete($id)
{
    global $sessione;
    $esito = ERR_UTENTE_NONE;

    // verifica che l'ID sia un numero intero
    if(is_id($id)) {

        // definisce la query per chiamare la stored-procedure
        $query = "SELECT utente_delete(".$id.",".$sessione->id_utente().")";

        // esegue la query e torna l'esito dell'operazione
        $esito = db_exec_result($query);

        // verifica se il codice indichi un errore
        $errore = utente_errore($esito);
        if($errore)
            throw new UIException(S_ERR_UTENTE, S_ERR_UTENTE_DELETE, $errore);
    }    
    return $esito;
}

function utente_load($id)
{
    $utente = null;

    // legge i dati dell'utente */
    if(is_id($id)) {
        $utente = db_exec_object("SELECT * FROM utente_load WHERE id = ".$id);
    }

    // verifica che i dati ci siano..
    if(is_null($utente))
        throw new UIException(S_ERR_UTENTE, S_ERR_UTENTE_LOAD, S_ERR_UTENTE_NONE);

    return $utente;
}

Il sistema Web si limita a richiamare la stored-procedure utente_delete, passandole gli opportuni parametri, senza sapere né cosa faccia la funzione né su quale DB agisca. Allo stesso modo, richiama i dati dalla vista utente_load, senza doversi preoccupare di quali campi debbano essere richiamati.
Questo approccio ha diversi lati positivi:

Il codice SQL per la generazione della vista utente_load, così come viene prodotto dal sistema è:

CREATE VIEW utente_load AS
SELECT
  f.*
-- , u.username AS modificato_da
-- , x.descrizione AS xxx
-- , date_format(f.ts_creazione, '%d-%m-%y') AS creato_fmt
FROM
  utente f
--   utente u
-- WHERE
--   f.id_modifica = u.id
;

Le righe a commento, inserite automaticamente dal sistema, possono essere utilizzate per aggiungere delle informazioni alla vista, come la decodifica di eventuali codici numerici o delle date in formato g/m/a.
Il codice della stored-procedure di inserimento nuovo utente è:

CREATE FUNCTION utente_insert (
  _username    VARCHAR(80)
, _password    VARCHAR(80)
, _nome        VARCHAR(80)
, _cognome     VARCHAR(80)
, _e_mail      VARCHAR(250)
, _attivo      BOOLEAN
, _id_modifica INTEGER
)
RETURNS INTEGER DETERMINISTIC
BEGIN
    DECLARE _id       INTEGER DEFAULT -1;
    DECLARE _esito    INTEGER DEFAULT 0;
    DECLARE _count    INTEGER DEFAULT 0;

    -- verifica eventuali duplicazioni
    SELECT count(*) INTO _count FROM utente WHERE (username = _username);
    IF _count > 0 THEN
      RETURN -2;
    END IF;

    -- verifica che l'utente esista
    SELECT count(*) INTO _count FROM utente WHERE (id = _id_modifica) AND (attivo = true);
    IF _count = 0 THEN
        RETURN -3;
    END IF;

    -- verifica lo username
    IF _username IS NULL OR _username = '' THEN
        RETURN -4;
    END IF;

    -- verifica la password
    IF _password IS NULL OR _password = '' THEN
        RETURN -5;
    END IF;

    -- verifica il nome
    IF _nome IS NULL OR _nome = '' THEN
        RETURN -6;
    END IF;

    -- verifica il cognome
    IF _cognome IS NULL OR _cognome = '' THEN
        RETURN -7;
    END IF;

    -- verifica l'indirizzo e-mail
    IF _e_mail IS NULL OR _e_mail = '' OR e_mail_valida(_e_mail) = FALSE THEN
        RETURN -8;
    END IF;

    -- inserisce i dati
    INSERT INTO utente(
      username
    , password
    , nome
    , cognome
    , e_mail
    , attivo
    , id_modifica
    )
    VALUES(
      _username
    , sha1(_password)
    , _nome
    , _cognome
    , _e_mail
    , _attivo
    , _id_modifica
    );

    -- definisce l'ID del record
    SET _id = last_insert_id();

    RETURN _id;

END;

La sequenza di controlli:

IF _campo IS NULL OR _campo = '' THEN
    RETURN errore;
END IF;

non è particolarmente elegante, ma è più facile da generare automaticamente. :-)
Ciascun codice di errore è riportato nella funzione PHP utente_errore:

function utente_errore($result)
{
    $errore = null;
    switch($result){
        case ERR_UTENTE_COGNOME : $errore = S_ERR_UTENTE_COGNOME ; break;
        case ERR_UTENTE_DUE     : $errore = S_ERR_UTENTE_DUE     ; break;
        case ERR_UTENTE_E_MAIL  : $errore = S_ERR_UTENTE_E_MAIL  ; break;
        case ERR_UTENTE_GRANT   : $errore = S_ERR_UTENTE_GRANT   ; break;
        case ERR_UTENTE_NOME    : $errore = S_ERR_UTENTE_NOME    ; break;
        case ERR_UTENTE_NONE    : $errore = S_ERR_UTENTE_NONE    ; break;
        case ERR_UTENTE_PASSWORD: $errore = S_ERR_UTENTE_PASSWORD; break;
        case ERR_UTENTE_SOSPESO : $errore = S_ERR_UTENTE_SOSPESO ; break;
        case ERR_UTENTE_USERNAME: $errore = S_ERR_UTENTE_USERNAME; break;
    }
    return $errore;
}

Le costanti numeriche per i codici di errore e le corrispondenti stringhe per i messaggi di notifica sono definite nel file utente.inc.

Questo è un esempio di codice per la pagina di modifica dati utente:

try {

    // verifica che l'utente abbia i privilegi necessari
    // per accedere alla pagina
    acl_check(SLUG_UTENTE_FORM);

    // aggiorna la sequenza delle pagine visitate
    $sessione->path_aggiorna();

    // legge l'ID dell'utente dai parametri in GET
    // la funzione param esegue gli opportuni controlli
    // per la bonifica dei dai in ingresso
    $id = param($_GET, 'p_id', $sessione->id_utente());

    // legge i dati dell'utente *
    $utente = utente_load($id);
    if(is_null($utente))
        throw new Exception(S_ERR_UTENTE_NONE);

    // definisce il titolo della pagina
    $titolo = S_UTENTE_DATI." ".$id;

    // apre la pagina
    html_begin();
    html_head(SLUG_UTENTE_FORM, $titolo);
    html_body_begin(SLUG_UTENTE_FORM);
    html_header($titolo);

    // visualizza la maschera di modifica dati utente
    utente_form($utente);

    // chiude la pagina
    html_body_end();
    html_end();

} catch (UIException $e) {

    errore_eccezione($e);
}

Come è facile immaginare, Modella genera un numero elevato di file (almeno quindici per ciascuna entità gestita), ma questo è un problema relativo, perché, in fase di creazione del sistema, il codice è in gran parte già pronto e richiede solo un numero limitato di modifiche, mentre in fase di modifica/debug, l’associazione diretta di ciascuna funzione a uno script permette di identificare rapidamente il punto in cui è necessario intervenire.

Un altro lato positivo di questo paradigma è che tutte le entità di tutti i sistemi sono gestite pressocché nello stesso modo; quindi, una volta imparato il funzionamento del sistema per una determinata entità si è in condizione di operare con (relativa) facilità anche su altre entità o altri sistemi.

Un’ultima nota, relativa alla sicurezza: il paradigma su cui è basato Modella implementa in maniera automatica, tutta una serie di procedure e di accorgimenti che incrementano la sicurezza del sistema, indipendentemente dalla specifica esperienza o dal livello di consapevolezza dei programmatori.