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.

4 commenti:

;) ha detto...

sei il mio idolo!!!

Grazie mille per le utilissime dritte!

ciao!

Massimo ha detto...

Ti ringrazio dei complimenti. Fa piacere sapere che quello che scrivo è utile a qualcuno!

Anonimo ha detto...

Grazie per la disponibilità a condividere le tue conoscenza!!

Ciao
Simon

Mauro ha detto...

Ciao,
ho incontrato per caso la tua pagina tramite una ricerca su google; sono novizio di php e la tua guida mi è stata utilissima.
Complimenti, è molto chiara ed esauriente.
Ti ho inserito nei bookmarks e mi riprometto di leggerti con più attenzione (ora ho fretta di terminare il lavoro).
Mauro

Posta un commento

Nota. Solo i membri di questo blog possono postare un commento.