venerdì 28 settembre 2007

Joomla 1.5: Continuiamo lo Sviluppo del Componente di Esempio

Riprendiamo il lavoro sul componente di esempio per Joomla 1.5.

Per chi se le fosse perse ecco i link alle puntate precedenti

Al punto in cui siamo arrivati l'area di amministrazione del componente manca di tutta una serie di funzioni standard che devono essere presenti in ogni componente che si rispetti. E questo sarà pure solo un componente di esempio, ma al rispetto ci tiene!

Inizieremo con l'aggiungere una funzione standard di paginazione record alla tabella per la visualizzazione dei dati inseriti dall'utente tramite il modulo di immissione dati.

Model

La prima parte da modificare è il codice della classe relativa all'oggetto Model del backend.

Riporto per chiarezza tutto il codice della classe MyFormModelMyForm. Se (con molta pazienza!) avete costruito il componente seguendo passo passo gli articoli precedenti potete aggiungere o modificare solo le parti in blu, le altre le dovreste avere, una controllatina non fa male però.

Il file è myform.php in
.../administrator/components/com_myform/models

class MyFormModelMyForm extends JModel {
  var $_total = null;
  var $_pagination = null;
  function __construct() {
    parent::__construct();
    global $mainframe, $option;

    $limit = $mainframe->getUserStateFromRequest('global.list.limit', 
    'limit', $mainframe->getCfg('list_limit'), 'int');
    $limitstart = $mainframe->getUserStateFromRequest($option.
    'limitstart', 'limitstart', 0, 'int');

    $this->setState('limit', $limit);
    $this->setState('limitstart', $limitstart);
  }
  function _buildQuery() {
    $query = ' SELECT * FROM #__myform ';
    return $query;
  }
  function getTotal() {
    if (empty($this->_total)) {
      $query = $this->_buildQuery();
      $this->_total = $this->_getListCount($query);
    }
    return $this->_total;
  }
  function getPagination() {
    if (empty($this->_pagination)) {
      jimport('joomla.html.pagination');
      $this->_pagination = new JPagination($this->getTotal(),
      $this->getState('limitstart'), $this->getState('limit'));
    }
    return $this->_pagination;
  }
  function getData() {
    $query = $this->_buildQuery();
    $pagination = $this->getPagination();
    $data = $this->_getList($query, $pagination->limitstart,
    $pagination->limit);
    return $data;
  }
}

Andiamo con ordine.
La funzione di paginazione record ha bisogno di due parametri fondamentali: il numero di record da visualizzare per ogni pagina (limit) e il numero della pagina su cui ci si trova (limitstart).

Il valore di questi parametri varia quando l'utente si sposta di pagina in pagina o modifica il numero delle righe da visualizzare con la lista di selezione in fondo alla tabella.

Il costruttore della classe utilizza il metodo getUserStateFromRequest() per recuperare i valori di limitstart e limit.

Funzioni che gestiscono queste, così definite nella documentazione ufficiale, variabili di stato utente (user state variable) si incontrano spesso esaminando il codice di componenti: getUserState, setUserState, getUserStateFromRequest, tutte definite in .../libraries/joomla/application/application.php come metodi della classe JApplication.

Il termine user state variable può confondere. Tutto sommato si tratta però di un concetto semplice: sono variabili che mantengono il loro valore attraverso diverse esecuzioni dello script perché sono salvate come variabili di sessione.

L'istruzione

$limit = $mainframe->getUserStateFromRequest('global.list.limit', 
  'limit', $mainframe->getCfg('list_limit'), 'int');

svolge le seguenti operazioni.

Legge il valore della variabile di stato (o se vi torna meglio di sessione) global.list.limit attraverso il metodo getUserState() e ritorna questo valore, a meno che un nuovo valore sia passato allo script tramite $_REQUEST['limit']. In questo caso è questo valore che viene salvato come nuova variabile di sessione (tramite setUserState()) ed è ritornato dalla funzione.

Se global.list.limit non esiste come variabile di sessione e nessun valore è passato in $_REQUEST['limit'], la funzione ritorna il valore di default che abbiamo passato come terzo argomento. In questo caso $mainframe->getCfg('list_limit') che non è nient'altro che il valore predefinito della lunghezza (cioè del numero delle righe per pagina) di tutte le tabelle dell'area di amministrazione, impostabile tramite Configurazione Globale -> Sito.

Quanto detto per $limit vale ovviamente per $limitstart.

Entrambi i valori sono salvati come stati di Model tramite il metodo setState() della classe JModel.

Implementiamo un metodo nuovo getTotal(). Questo metodo si limita a passare indietro il valore di ritorno del metodo privato _getListCount(), definito nella classe JModel, che riceve come argomento una query SQL (definita in _buildQuery()) e restituisce il numero totale dei record estratti dal database in base ad essa.

Inseriamo poi il metodo getPagination() che crea un'istanza della classe JPagination, la inizializza con i valori del numero totale dei record (ricavato da getTotal()), il numero della pagina da visualizzare (stato limitstart), il numero dei record da visualizzare su una pagina (stato limit) e restituisce l'oggetto. Vedremo poi come e da quale funzione sarà utilizzato.

Poiché i valori di limit e limitstart erano stati salvati nel costruttore come stati dell'oggetto Model tramite il metodo setState(), vengono ora recuperati con il reciproco getState().

Infine il metodo getData() dell'oggetto Model deve essere modificato per supportare la paginazione dei record. Il metodo privato _getList() (di JModel) deve ricevere oltre alla query sql (restituita da _buildQuery()) come già accadeva nelle precedenti versioni del componente, anche i valori di limit e limitstart senza i quali sarebbe sempre restituita la lista completa dei record estratti in base alla query senza alcuna paginazione.

View

Va poi modificata la classe MyFormViewMyForm. Il file è view.html.php in
.../administrator/components/com_myform/views/myform/

Come al solito le righe aggiunte sono in blu

class MyFormViewMyForm extends JView {
  function display($tpl = null) {
    $items =& $this->get('Data');
    $pagination = & $this->get('Pagination');
    $this->assignRef('items', $items);
    $this->assignRef('pagination', $pagination);
    parent::display($tpl);
  }
}

Veramente non c'è molto da dire. Ricordo solo che quando un oggetto Model contiene un metodo il cui nome inizia con get, l'oggetto View a cui detto oggetto Model è stato assegnato può accedere al valore di ritorno del metodo come se si trattasse di una sua proprietà il cui nome è uguale al nome del metodo meno il get iniziale.

Quindi

$pagination = & $this->get('Pagination');

assegna a $pagination l'oggetto creato dal metodo getPagination() che abbiamo implementato nell'oggetto Model poco fa.

Anche di assignref() si è già parlato negli articoli precedenti. Alla variabile $pagination viene assegnato un identificativo pagination che verrà utilizzato per riferirsi all'oggetto nel template.

Template

L'ultima modifica riguarda il template. Il file è default.php in
.../administrator/components/com_myform/views/tmpl/

defined('_JEXEC') or die('Restricted access');

$trh = '<thead><tr>
<th width="20%%">%s</th><th width="20%%">%s</th>
<th width="20%%">%s</th><th width="20%%">%s</th>
<th width="20%%">%s</th></tr></thead>';
$tr = '<tr>' . str_repeat('<td>%s</td>', 5) . '</tr>';
$html = '<form action="index.php" method="post" name="adminForm">
<table class="adminlist">';
$html .= '<tfoot><tr><td colspan="5">' . 
$this->pagination->getListFooter() .
'</td></tr></tfoot>';

$html .= sprintf($trh, JText::_('Nome'), JText::_('Cognome'), 
JText::_('Telefono'), JText::_('Indirizzo'), JText::_('Note'));
foreach($this->items as $item) {
  $html .= sprintf($tr, $item->nome, $item->cognome, $item->telefono, 
  $item->indirizzo, $item->note);
}
$html .= '</table>';
$html .= '<input type="hidden" name="option" value="com_myform" />
  <input type="hidden" name="task" value="" /></form>';
echo $html;

Viene inserito un form di nome adminForm e due campi nascosti option e task. Non è il caso di dilungarsi molto su questo (magari in futuro si potrà approfondire), è una parte standard che deve essere presente nell'interfaccia di amministrazione di ogni componente Joomla.

L'istruzione

$this->pagination->getListFooter()

visualizza nel footer della tabella gli elementi di interfaccia necessari alla navigazione (vedi foto)

Siamo nel template per cui $this->pagination è l'oggetto di classe JPagination creato nel metodo getPagination() dell'oggetto Model e a cui è stato assegnato l'identificativo pagination con assignRef() nell'oggetto View. Ripercorrete la strada all'indietro guardando il sorgente se qualcosa non è chiaro.

Siamo alla fine. Come al solito le spiegazioni a parole fanno sembrare tutto più difficile, ma a guardare bene per inserire la funzione di paginazione abbiamo dovuto scrivere non più di una ventina di righe di codice PHP.

La versione del componente aggiornata con tutte le modifiche apportate in quest'articolo è scaricabile dal sito, file myform4.zip

La prossima volta aggiungeremo le funzioni di modifica e cancellazione record.

domenica 23 settembre 2007

Textpattern: Breve Guida per Iniziare

Mi è capitato recentemente di dover mettere mano ad un sito realizzato con Textpattern. Di solito non uso questo cms e avevo avuto modo di darci solo una rapida occhiata diverso tempo fa. Devo dire che la seconda impressione è stata migliore della prima e mi è venuta voglia di buttare giù qualche riga sull'argomento.

Tanto per evitare di parlare solo di noiosa teoria ho pensato di usare Textpattern per creare un mini sito partendo da zero e mostrando l'intera procedura passo passo, per quanto possibile.

I più volenterosi (a dire così sembra di essere a scuola) possono provare a creare lo stesso sito in diretta leggendo l'articolo. Potete lavorare in locale se avete installati sul vostro PC Wamp Xampp o simili, o comunque Apache + PHP + MySql, oppure online se disponete di uno spazio web.

Io ho creato per questo scopo un sito di esempio su Netsons.org in modo che anche coloro che hanno meno voglia di lavorare possano vedere il risultato finale.

La miglior cosa è cominciare dall'inizio.

Installazione

Per chi è abituato a cms ben più corposi (come ad esempio Joomla) un pacchetto di installazione di poco più di 300 KB fa una certa impressione. Non dico che un cms debba valutarsi a peso, ma viene da chiedersi come possa esserci tutto quello che serve. E' una cosa che è molto facile scoprire.

L'installazione è semplice. Poniamo abbiate decompresso il pacchetto di installazione e copiati via FTP tutti i file in una cartella textp del vostro dominio, sarà sufficiente aprire il browser e visitare l'indirizzo www.vostrodominio.ext/textp/textpattern/ per avviare l'installazione via web.

Selezionata la lingua desiderata, si devono fornire le usuali informazioni: indirizzo server MySql, nome database MySql, utente MySql con privilegi sul database specificato e relativa password. Il database e l'utente devono essere già esistenti quindi vanno creati in precedenza con PHPMyAdmin o altro strumento di amministrazione del database messo a disposizione dall'host.

Devono essere poi forniti l'indirizzo del sito e il percorso alla cartella di Textpattern. Basta di solito confermare i dati già proposti.

Fatto questo, alla pagina successiva ci viene chiesto di creare un file config.php copiarci dentro i dati della configurazione presentati in una casella di testo e inserire poi il file nella cartella www.vostrodominio.ext/textp/textpattern/

Se vi state chiedendo per quale ragione il file di configurazione non possa essere generato automaticamente dallo script fate pure, ma non lo chiedete a me perché è quello che mi sono chiesto anch'io.

L'ultimo passaggio è inserire il nome, email, login e password dell'amministratore (meglio non perdersele).

Terminata l'installazione si può accedere all'area di amministrazione di Textpattern all'indirizzo www.vostrodominio.ext/textp/textpattern/ mentre la parte pubblica del sito è all'indirizzo www.vostrodominio.ext/textp/

Organizzazione del contenuto

In Texpattern la principale suddivisione logica dei contenuti del sito è costituita dalle sezioni.

Se per esempio volessimo realizzare con Textpattern un portale informativo potremmo creare le seguenti sezioni: Chi Siamo, Articoli, Risorse, Contatti.

Creare una sezione

Una volta effettuato il login come amministratore del sito selezionare le linguette

Aspetto -> Sezioni

L'installazione dovrebbe aver creato due sezioni about e article. Il sito di prova ci piace in italiano per cui cambiamo il campo Nome sezione rispettivamente in chi-siamo (i nomi delle sezioni non possono contenere spazi) e articoli. Cambiamo poi il campo Titolo sezione in Chi Siamo (gli spazi vanno bene nei titoli delle sezioni) e Articoli.

Infine modifichiamo il campo Usa la pagina della sezione articoli da archive a default. Le modifiche devono essere salvate separatamente per ogni sezione.

Per il momento non ci serve creare altre sezioni. Se volessimo farlo basterebbe scegliere il nome della sezione e premere Crea. La sezione viene creata ed aggiunta alla fine dell'elenco. Solo a questo punto se ne possono modificare le proprietà.

Inserire articoli

Predisposte le sezioni che ci interessano si possono inserire gli articoli.

Contenuti -> Scrivi

Textpattern non utilizza alcun editor WYSIWYG. gli articoli possono essere formattati includendo direttamente i tag html oppure si può utilizzare Textile.

Textile

Textile consente di formattare il testo con una sintassi semplificata. I marcatori Textile saranno poi convertiti in XHTML.

Per esempio una porzione di testo racchiusa tra asterischi sarà visualizzata in grassetto.

questa *parola* è evidenziata -> questa parola è evidenziata.

Il link Aiuto Textile sulla pagina inserimento articolo mostra l'elenco completo dei tag Textile che è possibile utilizzare.

Sinceramente io preferisco preparare il contenuto in un editor html esterno e fare poi un copia e incolla. Se avete la stessa abitudine selezionate il link Opzioni avanzate sulla sinistra e modificate l'impostazione da Usa Textile a Non intervenire sul testo sia per Codice articolo che per Codice sommario.

Sezione e categorie articolo

Ogni articolo è assegnato ad una sezione. La sceglieremo da una lista delle sezioni che abbiamo precedentemente creato che si trova nella colonna destra della pagina inserimento articolo.

Alcune sezioni tendono ad essere molto più affollate di altre. La sezione Chi Siamo conterrà probabilmente un solo articolo di presentazione dell'autore/i del sito, mentre la sezione Articoli può contenere centinaia di articoli.

Per questo Textpattern offre un'ulteriore criterio di classificazione dei contenuti, la categoria. Immediatamente al di sopra della lista sezioni si possono selezionare fino a due categorie per l'articolo. Seguendo il link Modifica a fianco della lista di selezione della prima categoria si viene indirizzati alla pagina di inserimento/modifica categorie (accessibile anche tramite le linguette: Contenuti -> Categorie).

Quelle che ci interessano per ora sono le Categorie Articoli (prima colonna).

Per il nostro sito creiamo le seguenti categorie: PHP, Javascript, Ajax.

Creare queste categorie è facile. Basta inserire il nome nella casella di testo e premere Crea. Si possono inoltre rimovere le categorie di prova create dall'installazione che con ogni probabilità non ci sono utili.

Stato articolo

Prima di salvare un articolo bisogna impostarne lo stato da una lista che si trova nella colonna destra della pagina inserimento articolo. Se si desidera salvare una versione preliminare dell'articolo non visibile nella parte pubblica del sito in modo da poterla editare in seguito, si imposta uno stato di Bozza, quando l'articolo è pronto per la pubblicazione si imposta uno stato di Visibile. Per il momento possiamo trascurare gli altri possibili stati.

Nel sito di prova ho creato due articoli (chiaramente finti!)

  • Chi Siamo (sezione Chi Siamo, nessuna categoria)
  • Introduzione al PHP (sezione Articoli, categorie PHP)

Createli anche voi scrivendo le prime fesserie che vi vengono in mente, tanto servono solo da riempitivo.

Inserire un'immagine nell'articolo

Volete non mettere una foto dell'autore e/o prorietario del sito nell'articolo Chi Siamo? Direi sarebbe quasi imperdonabile per cui ecco come si fa (a dire il vero questo è uno dei modi per farlo).

Siamo sempre nell'area di amministrazione. Selezionare

Contenuti -> Immagini

In alto a fianco della casella Carica immagine premere Sfoglia, selezionare un'immagine dal vostro hard disk, premere poi Carica.

Nella pagina successiva dovreste vedere la vostra immagine. Per quello che ci serve adesso è necessario preoccuparsi di riempire solo questi campi.

  • Nome immagine si può lasciare il nome del file
  • Testo Alternativo inserire ad es. Una mia fotografia

Premere Salva. Se tutto è andato bene, tornati alla schermata precedente, dovreste vedere il nome dell'immagine nell'elenco. Annotatevi il suo numero di ID (il valore sotto la prima colonna).

Visualizzare poi l'elenco articoli

Contenuti -> Articoli

Cliccare nell'elenco l'articolo Chi Siamo. Si apre la schermata di modifica articolo. Selezionare Opzioni avanzate, inserire nel campo Immagine articolo il numero di ID (attenzione che inserire il nome non va bene) dell'immagine caricata in precedenza e salvare.

A questo punto abbiamo assegnato l'immagine all'articolo, resta da impostare dove debba essere visualizzata. Selezionare

Aspetto -> Moduli

Accertarsi (controllando al di sotto della casella di testo al centro della pagina) che il nome del modulo selezionato sia default e la tipologia article. Altrimenti sul lato destro della pagina cliccare default sotto la colonna Modulo.

Modificate la casella di testo in questo modo (la riga in grassetto è l'unica da inserire, le altre devono esserci già altrimenti siete sul modulo sbagliato!)

<h3><txp:permlink><txp:title />
</txp:permlink> &#183; <txp:posted /></h3>
<txp:article_image class="article-image" />
<txp:body />
<p>&#8212; <txp:author /></p>
<txp:comments_invite wraptag="p" />
<div class="divider">
<img src="<txp:site_url />images/1.gif" width="400" 
height="1" alt="---" title="" /></div>

In pratica modifichiamo il formato di default di un articolo dicendo a Textpattern di includere un'eventuale immagine assegnata all'articolo prima del corpo dell'articolo (<txp:body />) stesso.

Non dimenticate di salvare il modulo.

I tag in Textpattern

<txp:article_image class="article-image" /> è un tag.

Un tag, con una certa semplificazione, perché come vedremo ci sono tag che svolgono una diversa funzione, è un segnaposto. Rappresenta cioè posizione ed attributi di una qualche entità che fa parte del contenuto.

I tag consentono di creare modelli della struttura di ogni tipologia di contenuti ed altri elementi del nostro sito.

Il modulo che abbiamo appena modificato rappresenta la struttura predefinita di un articolo del sito.

  1. Titolo con link alla pagina dell'articolo (permalink).
  2. Data
  3. Immagine
  4. Testo articolo
  5. Nome autore
  6. Sezione Commenti
  7. Separatore

Questo modello serve a Textpattern per determinare posizione ed attributi dei vari elementi quando un articolo viene visualizzato.

Da notare che nel nostro esempio usiamo lo stesso modulo per ogni articolo, ma è possibile creare quanti moduli vogliamo ed assegnare ad articoli diversi moduli diversi.

Tornando al tag <txp:article_image />, la sua funzione è di indicare la posizione dell'immagine assegnata all'articolo (ricordiamo che questo si fa inserendo l'ID dell'immagine nel campo Immagine articolo sotto Opzioni avanzate nella schermata di inserimento/modifica articolo).

Quando l'articolo è visualizzato, Textpattern inserisce al posto del tag il codice html necessario alla visualizzazione dell'immagine (<img src="..." />).

Abbiamo anche incluso un attributo del tag (class="article-image") che indica di assegnare al tag html <img> che visualizza l'immagine la classe article-image.

Modificare attributi immagine nel foglio di stile

Nel modulo abbiamo inserito l'immagine sopra il corpo dell'articolo, ma se lasciassimo tutto così l'effetto sarebbe veramente brutto: vogliamo che l'immagine sia allineata a sinistra con il testo che scorre a destra e sotto di essa.

Per ottenere questo dobbiamo modificare il foglio di stile. Selezionare

Aspetto -> Stile

Aggiungere alla fine del foglio di stile di default (c'è solo quello per cui non si può sbagliare)

.article-image {
  float:left;
  margin: 0 10px 10px 0;
}

Creare un menu di navigazione

Vogliamo a questo punto inserire un menu di navigazione nella colonna di sinistra con i link alle sezioni che abbiamo creato. Selezioniamo.

Aspetto -> Pagine

Accertiamoci che quella visualizzata sia la pagina di default e facciamo questa modifica (la riga da inserire è solo quella in grassetto, riporto le righe immediatamente precedenti e successive per consentirvi di trovare con facilità il punto dove fare la modifica).

...
<!-- left -->
<div id="sidebar-1">
<txp:section_list break="li" wraptag="ul" />
<txp:linklist wraptag="p" />
</div>
...

Abbiamo usato un altro tag, <txp:section_list />. Questo tag viene rimpiazzato con la lista dei titoli delle sezioni ovviamente collegati alle rispettive pagine. L'attributo break="li" indica che ciascun elemento (titolo sezione) sarà racchiuso tra i tag html <li> e </li>, mentre l'attributo wraptag="ul" indica che l'intero blocco sarà racchiuso tra <ul> e </ul>.

Salvate e visualizzate il sito (linguetta Mostra il sito). Dovreste vedere una lista delle sezioni sulla colonna di sinistra. Se usate l'opzione Visualizza sorgente pagina del vostro browser vi renderete conto del codice html con cui Textpattern ha rimpiazzato il tag.

In conclusione (per ora)

Riassumendo abbiamo visto:

  • Come installare Textpattern
  • Come creare sezioni
  • Come scrivere un articolo con Textile o in html
  • Come creare categorie
  • Come gestire lo stato di pubblicazione di un articolo
  • Come caricare un'immagine ed assegnarla all'articolo
  • Come aggiungere tag a modulo e pagina di default e modificare il foglio di stile

Il risultato di quello che abbiamo fatto è visibile sul sito di esempio. Certo molto bello da vedere non è, ma all'estetica possiamo pensare in seguito.

Per ora è tutto.

martedì 18 settembre 2007

Joomla 1.5: Funzioni di Accesso al Database

Con il rilascio, avvenuto proprio in questi giorni, della versione RC2, la versione stabile di Joomla 1.5 non dovrebbe farsi attendere ancora per molto.

Anche se si trattasse di aspettare ancora qualche mese, le novità sono molte per cui è consigliabile che chi è interessato a sviluppare applicazioni (componenti, moduli o plugins) per questo cms cominci a fare conoscenza con il nuovo framework.

Io lo sto facendo ed ho pensato (e se questa sia stata una buona idea ho paura che potrete scoprirlo solo continuando a leggere) di iniziare a scrivere qualcosa sull'argomento.

Le funzioni di accesso al database sono un buon punto di partenza.

La classe JDatabase

L'interrogazione e la manipolazione dei dati contenuti nel database di sistema avviene attraverso metodi della classe JDatabase. Molti metodi che esamineremo sono metodi astratti, sono cioè soltanto definiti in JDatabase, ma implementati in classi derivate costruite per accedere ad uno specifico motore di database: nella versione attuale di Joomla queste classi sono JDatabaseMySQL e JDatabaseMySQLi rispettivamente per l'accesso a database MySQL tramite le funzioni delle estensioni PHP mysql o mysqli.

L'oggetto database di sistema

Quando si sviluppano componenti, moduli o plugins per Joomla normalmente non è necessario eseguire istruzioni di connessione a database in quanto è sufficiente ottenere un riferimento all'oggetto database di sistema e utilizzare la connessione già aperta ed inizializzata da Joomla. A questo scopo si utilizza il metodo statico getDBO() della classe JFactory. Così

$database = &JFactory::getDBO();

Tramite l'oggetto database di sistema si possono compiere tutte le normali operazioni di accesso ai dati.

Impostare una query

Prima di poter essere eseguita ogni query deve essere impostata tramite il metodo setQuery().

$database->setQuery('SELECT * FROM #__users');

Il metodo setQuery() sostituisce i caratteri #__ con il prefisso delle tabelle specificato in fase di installazione di Joomla. Se non sono state modificate le impostazioni predefinite la query impostata sopra sarà eseguita sulla tabella jos_users.

E' importante utilizzare sempre questa notazione standard quando si impostano query da eseguire sulle tabelle di sistema. Se, poniamo all'interno del codice di un componente, scrivessimo

$database->setQuery('SELECT * FROM jos_users');

la query fallirebbe in tutti i casi in cui l'utilizzatore ha modificato il prefisso predefinito per le tabelle del database di sistema.

Eseguire una query di selezione

Il framework di Joomla mette a disposizione diversi metodi per eseguire una query di selezione e memorizzare i risultati in una variabile o array.

Per i nostri esempi utilizzeremo un estratto della tabella utenti di un immaginario sito Joomla (molto immaginario!)

loadAssocList

Il metodo loadAssocList() ritorna il risultato di una query in un array associativo.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadAssocList();

$results è un array di array associativi. Per accedere ad un singolo record si usa

$results[numero_record][nome_campo]

Esempio

echo $results[0]['name'];

Produrrà come output il contenuto del campo name del primo record ritornato,

Topolino

Per iterare l'intero insieme dei risultati si può usare

for($i=0, $ct=count($results); $i < $ct; $i++) {
  echo $results[$i]['name'],' | ',$results[$i]['username'],
  ' | ',$results[$i]['email'],'<br />';
}

o, più compatto

foreach($results as $r) {
  echo $r['name'],' | ',$r['username'],' | ', $r['email'],
  '<br />';
}

In entrambi i casi l'output è

Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

A loadAssocList() si può passare come argomento opzionale il nome di un campo i cui valori saranno utilizzati come chiave dell'array dei risultati.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadAssocList('username');

echo $results['paperone']['email'];

Darà come output

paperone@hotmail.com

loadObjectList

Il metodo loadObjectList() ritorna il risultato di una query in un array di oggetti.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadObjectList();

$results è un array di oggetti. Si può accedere ad un singolo record in questo modo

$results[numero_record]->nome-campo

Quindi nel nostro esempio

echo $results[0]->name;

Produrrà come output il contenuto del campo name del primo record ritornato,

Topolino

E ancora un esempio di iterazione con foreach

foreach($results as $r) {
  echo $r->name,' | ',$r->username,' | ', $r->email,'<br />';
}

Output

Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

Esattamente come loadAssocList() anche loadObjectList() accetta come argomento opzionale il nome di un campo i cui valori costituiranno le chiavi dell'array dei risultati.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadObjectList('name');
echo $results['Eta Beta']->email;

Output

etabeta@yahoo.com

loadRow

Il metodo loadRow() ritorna il primo record della query come array di campi indicizzato numericamente. Si accede al valore di un campo indicandone la posizione nella lista dei campi dell'istruzione SELECT (o nella tabella qualora si usi SELECT *).

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$result = $database->loadRow();
echo $result[0], ' | ', $result[1], ' | ', $result[2];
Output
Topolino | topolino | topolino@gmail.com

loadRowList

Il metodo loadRowList() ritorna il risultato di una query in un array di array di campi. Analogamente a quanto abbiamo visto per loadRow() si accede al valore di un campo tramite indice numerico.

$results[numero_record][posizione_campo]

Esempio

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$results = $database->loadRowList();
foreach($results as $r) {
  echo $r[0],' | ',$r[1],' | ', $r[2],'<br />';
}

Output

Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

A loadRowList() si può passare la posizione di un campo come argomento opzionale. In questo caso l'array dei risultati viene indicizzato utilizzando come chiavi i valori del campo. Notate la differenza con loadAssocList() e loadObjectList() dove il parametro opzionale è il nome di un campo.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$results = $database->loadRowList(1);
echo $results['paperino'][0];

Il campo con posizione 1 nella lista dei campi selezionati con SELECT è username, quindi l'istruzione echo del campo 0 (name) del record con chiave paperino visualizzerà

Paolino Paperino

loadAssoc

Il metodo loadAssoc() ritorna il primo record della query come array associativo le cui chiavi sono costituite dai nomi dei campi.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$result = $database->loadAssoc();
echo $result['name'], ' | ', $result['username'], 
  ' | ', $result['email'];

Output

Topolino | topolino | topolino@gmail.com

loadResult

Il metodo loadResult() ritorna il valore del primo campo del primo record di una query.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
echo $database->loadResult();

Output

Topolino

Di solito si usa con funzioni SQL che ritornano un solo record. Esempio.

$database = &JFactory::getDBO();
$database->setQuery("SELECT COUNT(*) FROM #__users 
  WHERE username LIKE 'p%'");
echo $database->loadResult();

Ritorna il numero dei record il cui campo username inizia con la lettera p. Nel caso della nostra tabella di esempio l'output è quindi

3

loadResultArray

Il metodo loadResultArray() riceve come argomento la posizione di un singolo campo nella lista dei campi specificati con l'istruzione SELECT e restituisce un array i cui elementi sono costituiti dai valori del campo per tutti i record estratti dalla query.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$results = $database->loadResultArray(0);
print_r($results);

Output

Array
(
    [0] => Topolino
    [1] => Paperon De' Paperoni
    [2] => Pietro Gambadilegno
    [3] => Eta Beta
    [4] => Paolino Paperino
)

Eseguire query di aggiornamento, inserimento o cancellazione

Si è visto che le query di selezione (SELECT) si eseguono utilizzando metodi diversi a seconda del tipo di array / variabile in cui vogliamo ricevere i risultati.

La situazione è fortunatamente molto più semplice nel caso di query di inserimento (INSERT), aggiornamento (UPDATE) o cancellazione (DELETE), che sono tutte eseguite con lo stesso metodo (query()) dell'oggetto database di sistema.

Quindi è sufficiente un solo esempio. Topolino cambia indirizzo email!

$database = &JFactory::getDBO();
$database->setQuery("UPDATE #__users 
  SET email='topolino@hotmail.com' 
  WHERE username='topolino'");
$database->query();
echo 'Record modificati: ', $database->getAffectedRows();

Il metodo getAffectedRows() restituisce il numero dei record modificati dalla query. Nel nostro caso l'output sarà

Record modificati: 1

Gestione degli errori

In caso di errore tutti i metodi che ritornano un insieme di record estratti in base ad una istruzione SELECT (i vari loadxxx() per intendersi) restituiscono null, il metodo query() invece restituisce il valore false.

Negli esempi i valori di ritorno dei metodi di accesso al database non sono mai controllati per non appesantire il codice, ma in una applicazione reale questa non sarebbe certo una buona pratica di programmazione.

Di solito oltre a sapere che si è verificato un errore in una query, torna comodo sapere di quale errore si tratti. Per far questo si utilizzano i metodi getErrorNum() e getErrorMsg() della classe JDatabase che restituiscono il codice numerico e il messaggio esteso relativo all'errore generato dalla query più recente.

Esempio (notare il nome errato della tabella)

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__usrs');
$results = $database->loadObjectList();
if(!$results) {
 echo 'Errore: ', $database->getErrorNum(), '<br />';
 echo $database->getErrorMsg();
}

Output

Errore: 1146
Table 'xxx.jos_usrs' doesn't exist SQL=SELECT * FROM jos_usrs

La documentazione di MySQL contiene una lista dei codici di errore

Comporre una query con dati ricevuti dall'utente

Capita spesso di dover creare una query in base a parametri di ricerca inseriti dall'utente. In questi casi è essenziale utilizzare il metodo getEscaped() per filtrare la stringa SQL in modo che tutti i caratteri che hanno un significato particolare per il motore del database (ad esempio gli apici) siano opportunamente mascherati.

Questo per prevenire errori o peggio tentativi di effettuare delle sql injections.

Esempio (sbagliato)

$name = "Paperon De' Paperoni";
$sql = "SELECT email FROM #__users WHERE name = '$name'";
$database = &JFactory::getDBO();
$database->setQuery($sql);
$result = $database->loadResult();
if(!$result) {
  echo $database->getErrorMsg();
} else {
  echo $result;
}

L'apice in $name non può essere inserito come tale all'interno della stringa SQL perché è utilizzato come carattere delimitatore.

Infatti questo codice produce un errore

You have an error in your SQL syntax; check the manual that 
corresponds to your MySQL server version for the right syntax
to use near 'Paperoni'' at line 1 
SQL=SELECT email FROM jos_users WHERE name = 'Paperon De' 
Paperoni'

Questa è la versione corretta

$database = &JFactory::getDBO();
$name= "Paperon De' Paperoni";
$sql = "SELECT email FROM #__users WHERE
  name = '" . $database->getEscaped($name) . "'";
$database->setQuery($sql);
$result = $database->loadResult();
if(!$result) {
  echo $database->getErrorMsg();
} else {
  echo $result;
}

L'output questa volta è

paperone@hotmail.com

Paginazione dei risultati di una query

Per concludere vediamo un esempio di come il risultato di una query SQL possa essere suddiviso in pagine contenenti un numero prefissato di record.

//Record per pagina
$rpp = 3;
$database = &JFactory::getDBO();
//Calcola il numero totale dei record
$sql = 'SELECT count(*) FROM #__users';
$database->setQuery($sql);
$tot = $database->loadResult();
if($tot) {
  //Numero delle pagine
  $np = ceil($tot / $rpp);
  $sql =  'SELECT * FROM #__users';
  
  for($pag=0; $pag < $np; $pag++) {
    $database->setQuery($sql, $pag*$rpp, $rpp);
    $results = $database->loadObjectList();
    if(!$results) {
      echo $database->getErrorMsg();
      break;
    }
    echo '<br />Pagina ',$pag+1,'<br />', 
      '----------','<br />';
    foreach($results as $r) {
      echo $r->name,' | ',$r->username,' | ', $r->email,
      '<br />';
    }
  }
}
Che genera il seguente output
Pagina 1
----------
Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com

Pagina 2
----------
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

Il codice dovrebbe essere autoesplicativo (ottimista?).

Da notare che il metodo setQuery() è usato con due argomenti opzionali che non abbiamo visto fino ad ora e che servono ad includere nell'array dei risultati (nell'esempio $results) un sottoinsieme del totale dei record estratti in base alla query SQL.

Il primo argomento (offset) indica la posizione a partire dalla quale i record estratti dalla query devono essere ritornati nell'array dei risultati. Da tenere presente che il primo record estratto dalla query ha posizione zero.

Il secondo argomento indica il numero dei record da ritornare nell'array dei risultati.

Calcolato il totale dei record da visualizzare e conoscendo il numero dei record per pagina (3 nell'esempio), determinare il valore dei due argomenti di setQuery() per ciascuna pagina è solo matematica!

Fortunatamente siamo alla fine perché cominciavo ad averne più che abbastanza di personaggi Disney!

Quelle esaminate non sono tutte, ma sono sicuramente le più utili e frequentemente usate funzioni per l'accesso al database che il framework di Joomla mette a disposizione degli sviluppatori.

Alla prossima.

mercoledì 5 settembre 2007

Generare Html da script PHP

La necessità di includere in una pagina web il classico contatore di visite o un modulo per la richiesta di informazioni al webmaster sono stati per me, e credo per molti altri, l'occasione del primo approccio con il PHP. Infatti uno dei maggiori punti di forza del linguaggio è la facilità con cui il codice PHP può essere integrato in un documento html.

Per questo motivo come generare dinamicamente un documento html/xhtml da uno script PHP o integrare in uno stesso documento codice PHP e html/xhtml sono conoscenze basilari che dovrebbero far parte del 'libro dei trucchi' di qualsiasi sviluppatore che utilizzi questo linguaggio.

Nonostante questo, credo si tratti di argomenti su cui è comunque interessante spendere qualche parola. Quando si affrontano problemi di questo tipo sono spesso disponibili strade molto diverse per arrivare alla stessa soluzione, il che può confondere le idee ai programatori più inesperti.

Ma lasciamo da parte le chiacchiere e vediamo qualche esempio pratico.

Immaginiamo di dover scrivere la funzione di visualizzazione dei post relativi ad una discussione in uno script per la gestione di un forum. Già che stiamo immaginando, immaginiamoci anche di aver fatto la query per l'estrazione dei dati (sarà una JOIN tra una tabella Posts e una tabella Users, ma non è così importante saperlo per cui cosa lo dico a fare non lo so) e di avere il risultato in $posts, un array di record.

Questo è un esempio del suddetto array. Per quello che vogliamo dimostrare un array con un solo elemento andrà più che bene.

Array
(
  [0] => Array
    (
      [userId] => Pippo
      [userPic] => pippo.png
      [userEmail] => pippo@gmail.com
      [showEmail] => 1
      [userWeb] => http://pippo.blogspot.com
      [postDate] => 2007-09-01
      [postTitle] => Pippo post
      [postText] => Pippo post text. Lorem ipsum dolor
       sit amet, consectetuer adipiscing elit. 
    )
)

Il risultato che vogliamo ottenere quando viene visualizzato è più o meno questo.

Vediamo come arrivarci.

Html, PHP, poi Html e PHP e ancora ...

... Html fino a far girare la testa. Così

<?php
foreach($posts as $post) {
?>
<div class="post">
  <div class="side">
    <div class="user"><?php echo $post['userId'];?></div>
    <img src="<?php echo $post['userPic'];?>" />
    <br />
    <?php if($post['userWeb']) {?>
      <a href="<?php echo $post['userWeb'];?>">
        <img src="images/site.gif" alt="website" />
      </a>
    <?php } ?>
    <?php if($post['showEmail']) {?>
      <a href="mailto:<?php echo $post['userEmail'];?>">
        <img src="images/email.gif" alt="email" />
      </a>
    <?php } ?>
  </div>
  <div class="text">
    <div class="title"><?php echo $post['postTitle'];?></div>
    <div class="date"><?php echo $post['postDate'];?></div>
    <?php echo $post['postText'];?>
  </div>
  <div class="footer">&nbsp;</div>
</div>
<?php
}
?>

I problemi di un codice scritto in questa maniera mi pare siano evidenti. Innanzi tutto è difficile da leggere. Anche in un esempio così semplice, non dimentichiamoci che tutto quanto sopra riportato serve a generare solo una piccola parte di una pagina html, risulta difficoltoso seguire il flusso logico del programma in quanto il codice PHP è frammentato, l'apertura e la chiusura delle istruzioni condizionali e dei cicli devono essere incluse in distinti blocchi delimitati da <?php e ?> per far spazio all'html.

Il codice è poi difficile da mantenere e modificare. Per esempio si possono facilmente introdurre errori nel codice PHP quando si modifica la parte html e viceversa.

Generare codice html da PHP

Proviamo un'altra strada. Per evitare continui passaggi da html a PHP e le conseguenti continue aperture e chiusure dei tag delimitatori si può scegliere di fare tutto il lavoro in PHP e usare istruzioni echo per l'output della parte html.

Il codice diventa

<?php
foreach($posts as $post) {
  echo "<div class=\"post\">
  <div class=\"side\">
    <div class=\"user\">{$post['userId']}</div>
    <img src=\"{$post['userPic']}\" /><br />";
  if($post['userWeb']) {
    echo "<a href=\"{$post['userWeb']}\">
      <img src=\"images/site.gif\" alt=\"website\" />
    </a>";
  } 
  if($post['showEmail']) {
    echo "<a href=\"mailto:{$post['userEmail']}\">
      <img src=\"images/email.gif\" alt=\"email\" />
    </a>";
  } 
  echo "</div>
  <div class=\"text\">
    <div class=\"title\">{$post['postTitle']}</div>
    <div class=\"date\">{$post['postDate']}</div>
    {$post['postText']}
  </div>
  <div class=\"footer\">&nbsp;</div>
  </div>";
}
?>

Note per i principianti. Ogni istruzione echo è suddivisa su più righe per ragioni tipografiche (altrimenti la riga uscirebbe dal margine della pagina). Si ha quindi un'unica istruzione PHP (fate caso alla posizione dei punti e virgola) suddivisa su più righe di sorgente. Notate anche che per interpolare un elemento di un array in una stringa bisogna racchiuderlo tra parentesi graffe.

In questo modo ci siamo sbarazzati della serie infinita di <?php ... ?> e i blocchi if sono ben indentati e più facili da leggere. Ma, diciamocela tutta, proprio bello da vedere non è!

Il problema è che per poter interpolare le variabili (gli elementi dell'array) abbiamo usato i doppi apici per delimitare le stringhe da visualizzare con echo. Questo ci costringe a mascherare i doppi apici (" diventa \") usati come delimitatori dei valori degli attributi html (echo "<div class=\"post\"> ..."). Infatti è chiaro che se i doppi apici sono usati come delimitatori di una stringa non possono essere usati come tali all'interno di essa altrimenti si avranno una serie di errori PHP.

Una noia quando si scrive il codice perché si finisce sempre per dimenticare qualche doppio apice non mascherato da qualche parte.

Si può risolvere suddividendo le parti da visualizzare con echo in più sottostringhe concatenate tra loro.

<?php
foreach($posts as $post) {
  echo '<div class="post">
  <div class="side">
    <div class="user">' . $post['userId'] . '</div>
    <img src="' . $post['userPic'] .'" /><br />';
  if($post['userWeb']) {
    echo '<a href="' . $post['userWeb'] . '">
      <img src="images/site.gif" alt="website" />
    </a>';
  } 
  if($post['showEmail']) {
    echo '<a href="mailto:' . $post['userEmail'] . '">
      <img src="images/email.gif" alt="email" />
    </a>';
  } 
  echo '</div>
  <div class="text">
    <div class="title">' . $post['postTitle'] . '</div>
    <div class="date">' . $post['postDate'] . '</div>' .
    $post['postText'] .
  '</div>
  <div class="footer">&nbsp;</div>
  </div>';
}
?>

Decisamente meglio, almeno secondo me. Notate ancora una volta le istruzioni echo su più righe di sorgente. Poiché abbiamo usato i singoli apici per delimitare le stringhe, possiamo usare i doppi apici all'interno di esse senza mascheramento. Questo rende però impossibile l'interpolazione delle variabili che devono essere quindi concatenate alle costanti stringa con l'operatore ( . )

Volendo si può rendere più efficiente il codice tenendo presente che l'istruzione echo accetta un numero variabile di argomenti (una cosa che spesso si tende a dimenticare).

Questo ci consente di riscrivere il tutto in questo modo.

<?php
foreach($posts as $post) {
  echo '<div class="post">
  <div class="side">
    <div class="user">', $post['userId'], '</div>
    <img src="', $post['userPic'], '" /><br />';
  if($post['userWeb']) {
    echo '<a href="', $post['userWeb'], '">
      <img src="images/site.gif" alt="website" />
    </a>';
  } 
  if($post['showEmail']) {
    echo '<a href="mailto:', $post['userEmail'], '">
      <img src="images/email.gif" alt="email" />
    </a>';
  } 
  echo '</div>
  <div class="text">
    <div class="title">', $post['postTitle'], '</div>
    <div class="date">', $post['postDate'], '</div>',
    $post['postText'],
  '</div>
  <div class="footer">&nbsp;</div>
  </div>';
}
?>

Le sottostringhe non sono concatenate, ma passate come singoli argomenti separati da virgole. Non cambia molto dal punto di vista visivo, ma è più efficiente perché si evitano inutili operazioni di concatenazione di stringhe.

La sintassi heredoc

Forse meno frequente, ma a mio parere molto utile quando si devono generare blocchi di codice html da uno script PHP, è l'uso della sintassi heredoc.

Se si scrive

$v = <<<EOD
Qui metti tutto quello che vuoi, incluse
parole tra 'singoli' o "doppi" apici
variabili php come $pippo o elementi di
array come {$ar['pippo']}
EOD;

Tutto quello che si trova dopo il primo EOD e prima del secondo EOD viene assegnato alla variabile $v. Poiché i singoli o doppi apici non vengono usati per delimitare la stringa, si possono usare liberamente nel testo senza mascheramenti (non servono \' o \"). Le variabili interpolate vengono espanse (cioè sostituite con il loro valore) esattamente come avverrebbe se avessimo delimitato la stringa con i doppi apici, anche se è comunque necessario racchiudere tra parentesi graffe elementi di array ed espressioni complesse.

EOD è solo un'etichetta, se ne può usare una qualsiasi, però è essenziale che l'etichetta di chiusura si trovi esattamente ad inizio riga senza essere preceduta da nessun carattere (neppure spazi o tabulazioni).

Ovviamente tutto quello che si può assegnare ad una variabile può essere stampato direttamente quindi il nostro esempio riscritto con la sintassi heredoc appare così

<?php
foreach($posts as $post) {
  echo <<<HTML
  <div class="post">
  <div class="side">
    <div class="user">{$post['userId']}</div>
    <img src="{$post['userPic']}" /><br />
HTML;
  if($post['userWeb']) {
      echo <<<HTML
      <a href="{$post['userWeb']}">
        <img src="images/site.gif" alt="website" />
      </a>
HTML;
  } 
  if($post['showEmail']) {
    echo <<<HTML
    <a href="mailto:{$post['userEmail']}">
      <img src="images/email.gif" alt="email" />
    </a>
HTML;
  } 
  echo <<<HTML
  </div>
  <div class="text">
    <div class="title">{$post['postTitle']}</div>
    <div class="date">{$post['postDate']}</div>
    {$post['postText']}
  </div>
  <div class="footer">&nbsp;</div>
  </div>
HTML;
}
?>

Si può notare come i doppi apici che delimitano i valori degli attributi html non sono mascherati e allo stesso tempo le variabili (nell'esempio sono tutti elementi di array) sono inserite senza bisogno di spezzare la stringa in sottoparti unite dall'operatore di concatenazione ( . ) o separate da virgole come eravamo stati costretti a fare negli esempi precedenti.

Anche se questo esempio è probabilmente troppo breve per apprezzarne in pieno l'utilità, la sintassi heredoc è il sistema che preferisco per generare grossi blocchi di codice html dall'interno di uno script PHP.

Semplificare ulteriormente con printf

Quando si generano lunghi documenti html è inevitabile che certe parti tendano a ripetersi frequentemente. In questi casi l'istruzione printf ci può aiutare a semplificare il codice.

Prima l'esempio poi la spiegazione.

<?php
$glnk = '<a href="%s"><img src="%s" alt="%s" /></a>';
foreach($posts as $post) {
  echo <<<HTML
  <div class="post">
  <div class="side">
    <div class="user">{$post['userId']}</div>
    <img src="{$post['userPic']}" /><br />
HTML;
  if($post['userWeb']) {
    printf($glnk, $post['userWeb'], 'images/site.gif', 
    'website');
  } 
  if($post['showEmail']) {
    printf($glnk, $post['userEmail'], 'images/email.gif', 
    'email');
  } 
  echo <<<HTML
  </div>
  <div class="text">
    <div class="title">{$post['postTitle']}</div>
    <div class="date">{$post['postDate']}</div>
    {$post['postText']}
  </div>
  <div class="footer">&nbsp;</div>
  </div>
HTML;
}

printf viene utilizzato per visualizzare una stringa formattata. La stringa, passata come primo argomento a printf, contiene dei caratteri segnaposto che in visualizzazione sono sostituiti dal valore degli altri argomenti (chiaramente oltre alla stringa si devono passare tanti argomenti quanti sono i caratteri segnaposto usati).

Nell'esempio si usa sempre %s che indica che il relativo argomento sarà visualizzato come semplice stringa senza alcuna formattazione particolare (per maggiori dettagli ed esempi vedere la documentazione di printf).

Grazie alla sintassi heredoc e semplificando le parti ripetitive con printf si è arrivati ad un codice molto più pulito di quello da cui siamo partiti.

Come detto all'inizio, il PHP offre spesso strade diverse per fare la stessa cosa. Conviene sempre esaminare tutte le possibilità prima di buttarsi a scrivere codice.

Spero che quanto detto sia stato utile a qualcuno altrimenti non resta che sperare di scrivere qualcosa di più interessante la prossima volta.