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

P4A 3 Framework: La nuova release 3.2.0, realizzare una maschera con una riga di codice e tabella editabile

screenshot della grid di P4A 3.2.0
screenshot della grid di P4A 3.2.0
La nuova versione del framework PHP P4A si presenta nel “versioning” con un salto di minor di 2 punti (da 3.0.3 a 3.2.0). Questo indica che le novità sono molte e significative. Forse Fabrizio e Andrea erano in vena di regali natalizi…! Strano, però che non ci sia stato ancora un annuncio ufficiale, io l’ho scoperto quasi per caso. Avevo letto in questo post di Andrea che il lancio era imminente, ma non mi sarei aspettato di trovare nell’area download di sourceforge il pacchetto P4A 3.2.0 già disponibile. Siccome le nuove caratteristiche mi stuzzicavano, l’ho subito provato.

Fra le novità più rilevanti c’è il cambio di tipo di licenza che passa dalla AGPL3 alla LGPL3. Non sono un esperto in materia, e non so dirvi molto. Da quanto ho capito si tratta di una differenza formale che regolamenta l’utilizzo e interazioni fra Librerie, Applicazioni e Lavori combinati.

Passando alle note tecniche, a parte una serie di patch e fix che risolvono alcuni bug, le novità più interessanti sono due. Una è quella del nuovo widget P4A_grid. Si tratta in pratica di una tabella editabile, molto utile per modificare singoli campi senza dover per forza utilizzare il fieldset e la toolbar.
La P4A_grid eredita dalla P4A_table tutti i metodi e le proprietà, in aggiunta presenta i seguenti:

  • void autoSave ()
  • void getRows ($num_page, $rows)
  • void preChange ([$params = null])
  • void saveData ($obj, $params)

Non ho avuto tempo di provare tutto, ma ecco un piccolo test con una tabella di prova per un’ipotetica gestione fatture:

1
2
3
4
5
6
7
  // grid
  $this->build("p4a_grid", "grid")
  ->setWidth(600)
  ->setSource($this->source)
  ->setVisibleCols(array("numero", "descrizione", "cliente"))
  ->showNavigationBar()
  ->autosave();

Il risultato è quello della immagine in alto, chiaramente il campo “lookup” non è editabile, ma il risultato è comunque fantastico. Se poi ci fosse la possibilità di riconoscere automaticamente i campi boolean e renderizzarli con una ckeckbox, e le “lookup” in select sarebbe ancora meglio! (dal post di Andrea sembra che la cosa sia già in cantiere…) Se avesse a disposizione anche un metodo per aggiungere record e cancellarli, sarebbe davvero perfetta, benché con un poco di codice e l’utilizzo dei metodi newRow() e deleteRow() questo dovrebbe essere già possibile.

Dulcis in fundo una piccola chicca, che trovo fantastica!
La possibilità di generare un’intera maschera con tutte le funzionalità per interagire con una tabella, scrivendo due sole righe di codice:

1
2
3
4
5
6
7
8
class test_clienti extends P4A_Base_Mask
{
	public function __construct()
	{
		parent::__construct();
		$this->constructSimpleEdit("clienti");
	}
}

Ma forse due righe sono troppe… ecco come fare scrivendone una sola!

1
2
3
4
5
6
7
8
class test_clienti extends P4A_Simple_Edit_Mask
{
 
	public function __construct()
	{
	 parent::__construct("clienti");
	}	
}

Questo il risultato in uno screenshot:

screenshot della P4A_Simple_Edit_Mask
screenshot della P4A_Simple_Edit_Mask

Nel primo caso utilizzo un nuovo helper (oggetto comodissimo particolarmente per il riutilizzo del codice) che si chiama: P4A_Mask_constructSimpleEdit() e nel secondo una classe che deriva da P4A_Base_Mask

Ci sono numerose altre novità come le migliorie nel comportamento dei CSS, la maggiore aderenza agli standard XHTML ed altri ancora che sono elencati nel file CHANGELOG.

Conclusioni

In attesa della presentazione ufficiale, mi sono permesso di elencare i due aspetti che più mi sono piaciuti! Lunga vita a P4A!

Riferimenti ed approfondimenti:

P4A3 framework: un widget conta tempo che utilizza il plugin jQuery Countdown

Widget p4a CountdownAvendo la necessità di inserire in una maschera di un progetto, un timer contatempo, ho provveduto all’integrazione nel framework P4A 3, del plugin Countdown sviluppato con jQuery da Keith Wood.
P4A 3 supporta nativamente jQuery e, come nel caso di jClock, il porting è alquanto semplificato.
Al momento ho reso disponibili solo alcuni dei metodi, mi riprometto di finire l’integrazione appena avrò un po’ più di tempo!

Che cosa fa?

Mostra un contatempo con avvio sia in count-down che in count-up, configurabile nell’aspetto e nel comportamento attraverso opportuni metodi.

Elenco dei metodi:
NomeTipoDefaultNote
setSince()integernullImposta la data e l’ora di avvio in formato UNIX timestamp
setUntil()integernullImposta la data e l’ora di stop in formato UNIX timestamp.
setDisplayFormat()stringdHMS‘Y’ anni, ‘O’ mesi, ‘W’ settimane, ‘D’ giorni, ‘H’ ore, ‘M’ minuti, ‘S’ secondi. I caratteri minuscoli impostano la visualizzazione opzionale
setCompactFormat()stringfalseImposta il formato ridotto.
setDescription()string“”Imposta la descrizione del contatempo.
setLabels()string"['Years', 'Months', 'Weeks', 'Days', 'Hours', 'Minutes', 'Seconds']"Imposta il testo per le varie parti del contatempo.
setLabelsSingle()string['Year', 'Month', 'Week', 'Day', 'Hour', 'Minute', 'Second']Imposta il testo per le varie parti del contatempo se sono al singolare.
setCompactLabels()string['y', 'm', 'w', 'd']Imposta il testo in formato compatto.
setCompactLabelsSingle()string['y', 'm', 'w', 'd']Imposta il testo in formato compatto se è al singolare.
setTimeSeparator()string:Imposta il separatore per il tempo.
setServerTime()stringnullImposta l’offset per il fuso orario. Valori di esempio: ‘+1’, ‘+2’, ‘+3’, ‘-1’, ‘-2’, ‘-3’. etc.

Ecco il codice per inserire il contatempo in una maschera di esempio:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class test_countdown extends p4a_base_mask
{
 public function __construct()
 {
   parent::__construct();
   $this-&gt;setTitle("test Countdown");
   $this-&gt;build("P4A_jCountdown", "countdown")
    -&gt;setStyleProperty("width","400px")
    -&gt;setStyleProperty("float","none")
    -&gt;setStyleProperty("margin","auto")    
    //Se vogliamo impostare una data in particolare possiamo usare il
    //seguente formato:
    //-&gt;setSince($this-&gt;date2timestamp('2008-09-26 17:59:47'))
    //oppure per avviare il contatempo con l'orario attuale:
    -&gt;setSince(time())
    //Se vogliamo visualizzare sempre solo ore, minuti e secondi:
    //-&gt;setDisplayFormat('HMS')
    -&gt;setCompactFormat(false)
    //Per impostare un offset sul fuso orario:
    //-&gt;setServerTime('-1')
    -&gt;setDescription("test countdown");
 
   $this-&gt;frame-&gt;anchorCenter($this-&gt;countdown);
  }
 
  //Questa funzione converte una data in formato date in UNIX timestamp
  public function date2timestamp($value)
  {
    $date_time = explode(' ',$value);
    $date_part = explode('-',$date_time[0]);
    $time_part = explode(':',$date_time[1]);
    $res = mktime($time_part[0],$time_part[1],$time_part[2],
           $date_part[1],$date_part[2],$date_part[0]);
    return $res;    
  }
 
}

Il pacchetto completo della maschera di esempio è scaricabile qui

Note:

Ho notato che impostando l’orario attuale attraverso l’istruzione:
...
->setSince(time())
...
può capitare che venga impostato un valore di tempo successivo a quello di caricamento nel browser della pagina, probabilmente a causa della asincronicità dovuta al “motore Ajax” di P4A 3. In questi casi, il contatore non parte subito, ma al primo nuovo reload del widget. Per evitare questo comportamento, è possibile impostare il tempo attuale decrementato di 1 o 2 secondi, p.e.:->setSince(time()-1)

Conclusioni:

L’integrazione di plugin jQuery in P4A 3 è abbastanza semplice, i passi principali sono:

  • Definire le proprietà e i metodi per impostarle nella classe che descrive il widget
  • Effettuare l’overload del metodo getAsString()
  • Importare nel metodo getAsString() le librerie ed eventuali CSS con i metodi: addJavascript() e addCSS()
  • Sempre in getAsString() scrivere l’eco del codice javascript necessario.

Riferimenti ed approfondimenti:


ag ha scritto:

Bel widget, complimenti :) Potresti aggiungerlo insieme al clock nella sezione contrib del wiki…

P4A3 framework: un widget orologio che utilizza jClock e jQuery

Widget p4a clockHo approfittato della necessità di presentare un piccolo orologio in una maschera di un progetto al quale sto lavorando, per scrivere l’integrazione del plugin javascript jClock sviluppato con jQuery nel framework P4A 3.
Nulla di spettacolare, siamo d’accordo, ma la scelta è caduta su jClock perché P4A supporta nativamente jQuery, e mi è sembrata la cosa più logica seguire quella direzione.

Che cosa fa?

Mostra un orologio digitale con l’ora del client, configurabile nell’aspetto e nel comportamento attraverso opportuni metodi.

Elenco dei metodi:
NomeTipoDefaultNote
setTimeNotation()string24hValori possibili 12h o 24h. Determina il modo di visualizzazione dell’orario
setAm_pm()stringfalseSe impostato a “true”, mostra A.M./P.M. (Solo se timeNotation() = “12h”).
setUtc()stringfalseSe impostato a “true”, mostra il tempo utilizzando UTC.
setUtc_offset()integer0Valori possibili: da -12 a +12, imposta l’offset da UTC.
setFontFamily()stringNULLSe specificato, imposta il set di caratteri per lo stile CSS, altrimenti eredita.
setFontSize()stringNULLSe specificato, imposta la dimensione in pixel dei caratteri per il testo (p.e.: “14px“), altrimenti eredita.
setForeground()stringNULLSe specificato, imposta il colore del testo (p.e.: “white“), altrimenti eredita.
setBackground()stringNULLSe specificato, imposta lo sfondo del testo (p.e.: “#4b718a“), altrimenti eredita.

Il codice per inserire l’orologio in una maschera di esempio:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test_clock extends p4a_base_mask
{
 public function __construct()
 {
   parent::__construct();
   $this-&gt;setTitle("test Clock");
   $clock = $this-&gt;build("P4A_jClock","clock");
   $clock-&gt;setStyleProperty("width","150px");
   $clock-&gt;setStyleProperty("float","none");
   $clock-&gt;setStyleProperty("margin","auto");
   $clock-&gt;setTimeNotation("12h");
   $clock-&gt;setAm_pm(true);
   $clock-&gt;setFontSize("14px");
   $clock-&gt;setForeground("white");
   $clock-&gt;setBackground("#4b718a");
   $this-&gt;frame-&gt;anchorCenter($clock);
  }
}

Il pacchetto completo della classe, della libreria javascript e della maschera di esempio è scaricabile qui

Note:

Al momento, il sistema di scrittura del codice detto “chainability” non funziona solo con i metodi che ho dichiarato nella classe P4A_jClock. Mi riprometto di individuare e correggere l’errore! Sarà molto gradito il feedback di tutti coloro che volessero utilizzare questo widget, per individuare e correggere eventuali altri errori e per apportare nuovi miglioramenti.

Conclusioni:

E’ interessante soprattutto capire come scrivere widget per P4A. Infatti, sebbene P4A framework, particolarmente nella nuova versione, offra già parecchie feature, capita comunque di aver necessità di qualcosa di particolare per le proprie applicazioni!
In futuro potrebbe essere interessante creare un widget per un orologio analogico, ne ho visti di molto belli qui: CoolClock – The Javascript Analog Clock. e già ci sto facendo un pensierino…

Riferimenti ed approfondimenti:


Fabrizio Balliano ha scritto:

Ciao Mario, per “abilitare” la chainability devi semplicemente aggiungere un “return $this” alla fine di ogni metodo della classe, tutto qui :-))) ottimo widget comunque soprattutto per l’utilizzo “non mascherato da p4a” di jquery!
02.07.08 09:00
Mario Spada ha scritto:

Grazie Fabrizio! Codice corretto, adesso funziona anche la chainability per i metodi dichiarati nel widget.
07.07.08 18:34

P4A3 framework: un helper per pulire la directory tmp in ambiente Windows

P4A 3 Logo

Ho scritto questo piccolo “helper” per ovviare ad un piccolo inconveniente che avviene con P4A3, esclusivamente in ambiente Windows. Succede che, se viene utilizzata la funzione: P4A_Output_File(), ad esempio per generare un file pdf, il file temporaneo non viene più cancellato. Ho potuto verificare che in ambiente Linux questo non succede, e tutti i file generati vengono eliminati una volta passati al browser.

Non sono riuscito a capire esattamente il motivo ma credo che il problema risieda ancora una volta nel differente trattamento da parte di Windows dei separatori nei percorsi assoluti dei path (Windows utilizza i \ invece dei /).
Il problema, anche se può risultare fastidioso alla lunga, per l’accumularsi di un eccessivo numero di files nella directory /uploads/tmp, non è di certo grave, e soprattutto, è molto semplice da risolvere. E’ sufficiente infatti lanciare un metodo che ripulisca” la cartella dai file temporanei più vecchi di n ore (o giorni, o minuti).

Questo è il codice della funzione, che per comodità può essere gestito come “helper”, ovvero un metodo personalizzato prontamente disponibile da qualsiasi maschera della nostra applicazione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function p4a_mask_clean_old_tmp_files($obj, $olderThan)
{
 $fileDeleted = 0;
 $expire_time = empty($olderThan[0]) || !is_numeric($olderThan[0])? 24: $olderThan[0];
 $fileTypes = array("_p4a_*","cache_*");
 if (P4A_OS == 'windows') {
  define('TEMPDIR','\tmp\\');
  $dirToClean =  P4A_UPLOADS_DIR.TEMPDIR;
  foreach ($fileTypes as $fileType){
   foreach (glob($dirToClean . $fileType) as $filename) {
   $fileCreationTime = filectime($filename);
   $fileAge = time() - $fileCreationTime;
    if ($fileAge > ($expire_time * 3600)){
     if (unlink($filename)){
      $fileDeleted++;
     }
    }
   }
  }
 }
 return $fileDeleted;
}

Il file si deve trovare nella directory /libraries all’interno della directory principale della nostra applicazione, e si deve chiamare: p4a_mask_clean_old_tmp_files.php. Il default è impostato su 24 ore.
Per cambiare la scala del tempo trascorso da ore a minuti è necessario cambiare questa riga:
if ($fileAge > ($expire_time * 3600)){
in
if ($fileAge > ($expire_time * 60)){

Il metodo viene richiamato in questo modo:

$res = clean_old_tmp_files(1); // cancella i files più vecchi di un'ora

La funzione restituisce il numero di files cancellati oppure 0

Conclusioni:

Ho già segnalato questa piccola anomalia a Fabrizio Balliano in questo commento, e credo che ben presto, magari con la release ufficiale di P4A3 annunciata per il 24/06/2008, il problema verrà risolto. Nel frattempo, gli utenti Windows, possono usare questo semplice “helper”.

Riferimenti ed approfondimenti:


Fabrizio Balliano ha scritto:

hum, io aveto testato e vedendo che funzionava correttamente non mi sono piu’ interessato alla questione, ma non sapevo che il problema fosse su windows… ora per martedi’ sara’ complicato risolverlo, vedo che posso fare.
22.06.08 11:42
Fabrizio Balliano ha scritto:

bug risolto :-)
22.06.08 13:04
Mario Spada ha scritto:

Grande Fabrizio, e a tempo di record!!

P4A3 framework: stampa di pdf con le librerie Zend_Pdf

logo pdfNelle precedenti versioni del framework P4A non c’era nessuna libreria interna per la generazione dei documenti pdf.
Era quindi necessario adottare librerie esterne quali ezPdf o FPDF.
La versione 3, invece, incorpora la libreria Zend_Pdf che è integrata nello Zend framework.
A dire il vero, mi ero ben abituato ad ezPdf, e nulla vieta di utilizzarlo ancora, ma mi è sembrato doveroso provare anche le funzionalità delle classi Zend, che oltretutto hanno l’innegabile vantaggio di essere comprese nel framework. Da una prima analisi sommaria, mi sembra che hanno un discreto numero di funzioni utili per il disegno, ma non altrettanto per la manipolazione e gestione del testo. Anche per la formattazione delle tabelle, non mi sembra che ci sia nulla di simile a quanto offre ezPdf.

Passiamo all’esempio pratico: per la prova ho utilizzato la solita applicazione di esempio “Products catalogue”, implementando un pulsante che stampa la scheda del prodotto selezionato. Qundi nel costruttore di products.php ho inserito il seguente codice per creare il bottone e associargli l’evento per la stampa della scheda.

...
// Pdf print button ---------------------------
$this-&gt;build("p4a_button", "printProduct")
	-&gt;setLabel("Print pdf")
	-&gt;implement("onclick", $this, "printDoc");
$this-&gt;build("p4a_fieldset", "fs_pdf")
	-&gt;setLabel("Print product card")
	-&gt;anchor($this-&gt;printProduct);
// --------------------------------------------
...

Vediamo ora il codice del metodo per la stampa vera e propria del pdf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public function printDoc()
{
  require_once(P4A_SERVER_DIR."/p4a/".P4A_LIBRARIES_PATH."/Zend/Pdf.php");
 
  $imageName = $this-&gt;fields-&gt;picture-&gt;getNewValue(0);
  if (!empty($imageName)){
    $imagePath = P4A_SERVER_DIR . P4A_UPLOADS_PATH .
                  "/" . $imageName;
    $image = Zend_Pdf_Image::imageWithPath($imagePath);
  }
  $brand = $this-&gt;fields-&gt;brand-&gt;getNewValue();
  $model = $this-&gt;fields-&gt;model-&gt;getNewValue();
  $price = $this-&gt;fields-&gt;price-&gt;getNewValue();
 
  $description = $this-&gt;fields-&gt;description-&gt;getNewValue();
  $description = strip_tags($description);
  $wrapDescription = wordwrap($description, 42, "§");    
  $arrDescription = split("§",$wrapDescription);
 
  $doc = new Zend_Pdf; 
  $page1 = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4); 
  $page1-&gt;setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_COURIER),14); 
  $doc-&gt;pages[] = $page1; 
  if (!empty($imageName)){
    $page1-&gt;drawImage($image, 50, 720, 150, 750);
  }
  $page1-&gt;drawText("Marca: ".$brand,50,700,'UTF-8'); 
  $page1-&gt;drawText("Modello: ".$model,50,680,'UTF-8');
  $page1-&gt;drawText("Prezzo: ".$price,50,660,'UTF-8');  
  $page1-&gt;drawText("Descrizione: ",50,640,'UTF-8');
  $y = 640;
  foreach ($arrDescription as $segment){
    $page1-&gt;drawText($segment,160,$y,'UTF-8');
    $y -= 20;
  }
  $pdfData = $doc-&gt;render();
  P4A_Output_File($pdfData,"product.pdf"); 
}

Il campo “description” potrebbe essere anche piuttosto lungo, e poiché non ho trovato nessuna funzione o parametri per fissare un box di stampa entro il quale il testo possa essere formattato, ho dovuto preparare la stringa di testo dividendola in elementi di un array di stringhe di lunghezza prefissata. Per farlo senza spezzare le parole, ho usato la funzione php wordwrap() impostando come interruzione di testo un carattere molto poco usato: “§“. Quindi ho usato questo carattere come separatore dei segmenti di testo. Non è proprio una soluzione robustissima, sarebbe opportuno scrivere un metodo apposito più preciso, nell’attesa che le classi Zend_Pdf vengano arrichite come lascierebbe intendere questo post.

Ecco l’elenco dei formati pagina disponibili:

  • Zend_Pdf_Page::SIZE_A4
  • Zend_Pdf_Page::SIZE_A4_LANDSCAPE
  • Zend_Pdf_Page::SIZE_LETTER
  • Zend_Pdf_Page::SIZE_LETTER_LANDSCAPE

E’ anche possibile specificare un formato personalizzato passando direttamente i valori ($x, $y) in punti di 1/72 di pollice.

Questi invece sono alcuni dei font disponibili:

  • Zend_Pdf_Font::FONT_COURIER
  • Zend_Pdf_Font::FONT_COURIER_BOLD
  • Zend_Pdf_Font::FONT_COURIER_ITALIC
  • Zend_Pdf_Font::FONT_COURIER_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_TIMES
  • Zend_Pdf_Font::FONT_TIMES_BOLD
  • Zend_Pdf_Font::FONT_TIMES_ITALIC
  • Zend_Pdf_Font::FONT_TIMES_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD
  • Zend_Pdf_Font::FONT_HELVETICA_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_SYMBOL
  • Zend_Pdf_Font::FONT_ZAPFDINGBATS

Infine, un esempio del documento pdf generato.

Per maggiori dettagli vi rimando ai link nel paragrafo: “Riferimenti ed approfondimenti”

Note:

E’ necessario scaricare l’ultima versione (2.99.9) preview di P4A3 che però non è ancora troppo stabile e soffre di qualche imperfezione. Quelle già note sono state corrette, quindi è necessario attingere al SVN per il corretto funzionamento del framework. In particolare, al momento è necessario prelevare il seguente sorgente dal SVN: p4a_db.php rev. 1762 che risolve un problema relativo agli apici usati nelle query. In alternativa si deve aspettare il nuovo imminente rilascio.
Infine, la funzione code>P4A_Output_File() genera un file temporaneo nella directory /uploads/tmp che quindi deve essere presente e avere i permessi di scrittura. Purtroppo non ho ancora capito perché i file temporanei non vengano eliminati alla fine della sessione, come mi sarei aspettato. Indagherò su questa anomalia, nel frattempo bisogna eliminarli manualmente per evitare che la directory si riempia di pdf!

Riferimenti ed approfondimenti: