P4A 3 Framework: helper per la gestione degli errori in saveRow() e deleteRow()

Errore di chiave duplicataHo scritto questi due helper per facilitare la gestione degli errori dovuti a query di aggiornamento e di cancellazione record. Gli helper sono una comodissima feature del framework P4A, si tratta dell’opportunità di scrivere funzioni personalizzate che possono essere raggiunte da qualsiasi maschera dell’applicazione. Questa caratteristica consente quindi un efficiente riutilizzo del codice, con grande vantaggio per la leggibilità dello stesso. Per costruire un helper, richiamabile da qualsiasi maschera, è necessario scrivere un file nella directory libraries, all’interno della propria applicazione salvandolo con un nome a piacere preceduto dal suffisso p4a_mask_. All’interno del file deve essere presente una funzione nominata nello stesso modo. Vediamo un esempio di albero di directory:

P4A
 |
 -- applications
      |
      -- gestione_fatture
          |
          -- objects
          |
          -- uploads
          |
          -- libraries  <-- qui! 

All’interno di questa directory posizioniamo i due file:

  • p4a_mask_savewithoutpain.php
  • p4a_mask_deletebreezily.php

Le due funzioni richiamano i metodi saveRow() e deleteRow() all’interno del costrutto PHP: try ... catch, intercettando gli eventuali errori e fornendo un messaggio di avviso senza interrompere il flusso del programma. Quando si utilizzano query di aggiornamento e cancellazione, è sempre bene fare dei controlli perché se si utilizzano tabelle InnoDB si rischia di rompere l’integrità referenziale, se si utilizzano indici di tipo UNIQUE si rischia la duplicazione, e così via. D’altra parte la ricerca tramite SELECT dei vincoli e/o delle chiavi duplicate è spesso troppo laboriosa, anche se in certi casi necessaria.
NOTA: try ... catch funziona solo con P4A 3.x, in quanto è disponibile a partire da PHP5.

Vediamo il codice di p4a_mask_savewithoutpain.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Funzione: Salva senza soffrire :-)
function P4A_Mask_saveWithoutPain($mask, $params)
{    
    list($source) = $params;
    $mask->source = $source; 
	  try{ 
      $mask->source->saveRow();       
    } catch (Exception $e) {  
 
      $res = $e->getMessage();
      preg_match("/\SQLSTATE\[(.+)\]/",$res, $results);
 
      if ($results[1] == '23000') {
        $msg = "Errore: chiave duplicata!";
      }
      else {
        $msg = "Si e' verificato un errore nel salvataggio dei dati";
      }    
      $mask->warning($msg);  
    }
}

Le prime due righe di codice che ho spudoratamente copiato dagli helper di sistema, servono a ricevere gli oggetti: maschera chiamante e data source. Con try si tenta una saveRow(), se fallisce viene valutato il messaggio di errore per lo sbaglio più frequente: chiave duplicata. Negli altri casi viene segnalato un errore generico.

Questo, invece è il codice di p4a_mask_deletebreezily.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Funzione: cancella allegramente... :-)
function P4A_Mask_deleteBreezily($mask, $params)
{
    list($source) = $params;
    $mask->source = $source;
	  try{ 
      $mask->source->deleteRow();       
      } 
    catch (Exception $e) {  
      $res = $e->getMessage();
      $pos = strpos($res, "General error: 1451"); // Errore di rottura integrita'
                                                  // referenziale
      if ($pos==false) {
        $msg = "Si e' verificato un errore nella cancellazione";
      }
      else {
        $msg = "Errore: impossibile cancellare un record correlato";
      }
      $mask->warning($msg);  
    }
}

In questo caso ho valutato solo errori di cancellazione di record che abbiano un vincolo di relazione. Non ho ricercato nella descrizione il codice SQLSTATE perché in SQL lo stesso codice accomuna diversi casi. E’ quindi necessario utilizzare il numero di errore MySQL, che nel caso di rottura dell’integrità referenziale è 1451.

Ecco infine il codice da aggiungere alla nostra ipotetica maschera: (gli helper vengono richiamati omettendo il suffisso p4a_mask_ )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class test_fatture extends p4a_base_mask
{
 public function __construct()
  {
    parent::__construct();
    // DB Source
    $this->build("p4a_db_source", "source")
    // .....
    // codice della maschera...
    // .....
  }
  public function saveRow ()
  {
    $this->saveWithoutPain($this->source);
  }
  public function deleteRow()
  {
    $this->deleteBreezily($this->source);
  }
}

Infine vorrei ricordare gli helper di sistema che, grazie agli autori, ci fanno risparmiare parecchio lavoro!

  • P4A_Field_loadSelectByArray – Imposta un campo di tipo select da un array.
  • P4A_Field_loadSelectByTable – Imposta un campo di tipo select da una tabella.
  • P4A_Frame_anchorTabPane – Ancora un tab pane ad una maschera.
  • P4A_Mask_useToolbar – Imposta una toolbar e la ancora alla topbar.
  • P4A_Mask_setTableAsSource – Imposta un DB_source da una tabella.
  • P4A_Mask_constructSimpleEdit – Costruisce un’intera maschera! (disponibile in P4A 3.2.0)

Conclusioni

Credo che, grazie a strumenti di questo tipo, si può contribuire nello sviluppo Open Source, con poco sforzo e molto vantaggio per tutta la comunità!

Riferimenti ed approfondimenti:


Andrea ha scritto:

Ciao Mario,
complimenti per i gli helper, molto utili :)

Solo un appunto, io eviterei questa linea di codice
$mask->source = $source;
In pratica cosi’ facendo rifai un’assegnazione della variabile source della maschera che non serve: nel tuo esempio quando chiami $this->build(“p4a_db_source”, “source”) hai gia’ popolato la variabile source con un db_source.
Tra l’altro nel tuo esempio non da’ alcun fastidio ma in un caso teorico potrebbe essere un problema perche’ se l’utente inizializzasse la variabile source con un oggetto diverso chiamando quella riga di codice lo andresti a sovrascrivere con risultati del tutto inaspettati (immagina un utente che nella maschera abbia scritto $this->build(‘p4a_table’,’source’)).

Per risolvere il tutto basterebbe cambiare il codice in questa maniera:

try{
$source->saveRow();

Spero di esserti stato utile, a presto
Andrea

ps spero di non aver postato questo commento due volte, la prima volta il server mi ha dato errore… nel caso ignorami ;)
30.12.08 12:04
Mario Spada Author Profile Page ha scritto:

Grazie Andrea per la correzione, hai perfettamente ragione!
30.12.08 19:55