Separare il markup dal codice PHP

Ho avuto la necessità di realizzare un sito parzialmente statico, ovvero con contenuti statici e una leggera base programmativa. Uno dei problemi che ho cercato di risolvere è stato quello di separare quanto più possibile la parte di scripting dal contenuto html, tecnicamente parlando, separando il markup dalla logica. Questo per poter gestire il codice: a breve termine, dando modo al programmatore e al webdesigner di lavorare indipendentemente, e a lungo termine, per facilitare il riuso del codice (HTML e PHP) e la sua manutenibilità.
Il linguaggio scelto è PHP, uno dei linguaggi di scripting per il web più diffusi. Nelle varie soluzioni che qui riprenderò valuterò vari aspetti della stessa, con il chiaro scopo di cercare di realizzare un codice che fosse il più semplice possibie, e utilizzare l'OOP il meno possibile.

La base di partenza

Chiunque abbia provato a lavorare con PHP ha sicuramente lavorato con print e echo, quindi la situazione di partenza è proprio quella in cui il codice è frammisto a pezzi di HTML, il classico macello programmativo.

index.php

  1. <?php
  2. // funzioni di vario genere
  3. // inizializzazioni di variabili
  4. // if vari ed eventuali
  5. ?>
  6. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ...
  7. <html xmlns="<a href="http://www.w3.org/1999/xhtml"">http://www.w3.org/1999/xhtml"</a> lang="en">
  8. <head>
  9. <title>
  10. <?php
  11. if ($_GET['page']=="sect1") echo "Sezione 1 del mio sito";
  12. else echo "Il mio primo sito";
  13. ?>
  14. </title>
  15. </head>
  16. <body>
  17. <div id="menu"><a href="?page=sect1">Sezione 1</a></div>
  18. <div id="content">
  19. <?php
  20. if ($_GET['page']=="sect1") echo "<h1>Prova</h1><p>questa è una prova</p>";
  21. else echo "<h1>Benvenuti</h1><p>Questa è la home page</p>";
  22. ?>
  23. </div>
  24. </body>
  25. </html>

Faccio presente la differenza dell'uso dei " (doppi apici) e degli ' (apici singoli): con i doppi apici non occorre usare la concatenazione delle stringhe in quanto permetterà di valorizzare automaticamente le variabili (denotate da $). Questo invero non avviene se cercate di usare degli array.
Il codice sopra descritto di solito porta alla pazzia, specialmente quando il numero di righe inizia a superare il 500~1000.
Da buoni (?) programmatori allora proviamo a togliere il codice HTML dalla logica utilizzando una funzione stupenda che si chiama include() (la si trova invero perché una ricerca nel manuale online di php porta inesorabilmente lì).

Usare include()

La cosa interessante di include, è che funziona come una sostituzione letterale della riga di include() con il contenuto del file specificato, prima che lo script venga interpretato, consentendoci quindi di scrivere codice nel file incluso (altre variazioni della funzione sono require(), include_once() e require_once()). Quindi ci troveremo con la logica

index.php

  1. <?php
  2. // funzioni di vario genere
  3. // inizializzazioni di variabili
  4. if ($_GET["page"]=="sect1") $title = "Sezione 1 del mio sito";
  5. else $title = "Il mio primo sito";
  6. // if vari ed eventuali
  7. include("markup.php");
  8. ?>

e il markup:

markup.html

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ...
  2. <html xmlns="<a href="http://www.w3.org/1999/xhtml"">http://www.w3.org/1999/xhtml"</a> lang="en">
  3. <head>
  4. <title>
  5. <?php echo $title; ?>
  6. </title>
  7. </head>
  8. <body>
  9. <div id="menu"><a href="?page=sect1">Sezione 1</a></div>
  10. <div id="content">
  11. <?php
  12. if ($_GET['page']=="sect1") echo "<h1>Prova</h1><p>questa è una prova</p>";
  13. else echo "<h1>Benvenuti</h1><p>Questa è la home page</p>";
  14. ?>
  15. </div>
  16. </body>
  17. </html>

Sicuramente si può procere all'infinito in questa maniera, riducendo magari l'unica stringa php del markup in:

markup.php

  1. ...
  2. <?php
  3. if ($_GET['page']=="sect1") include("sect1.html");
  4. else include("home.html");
  5. ?>
  6. ...

ed infine il vero e proprio contenuto:

sect1.html

  1. <h1>Prova</h1>
  2. <p>questa è una prova</p>

home.html

  1. <h1>Benvenuti</h1>
  2. <p>Questa è la home page</p>

Includere manualmente?

L'unica debolezza di include è però la necessità di espandere ed interpretare il codice da includere, che influisce all'aumentare del volume delle pagine. L'alternativa è usarlo solo dove serve.

index.php

  1. <?php
  2. // funzioni di vario genere
  3. function fileRead($filename)
  4. {
  5. $fileContent = "";
  6. if ($file = fopen($filename, 'r'))
  7. {
  8. $fileContent = fread($file, filesize($filename));
  9. fclose($file);
  10. }
  11. return $fileContent;
  12. }
  13. // inizializzazioni di variabili
  14. // $page conterrà "markup.php" come stringa di testo
  15. $page = fileRead("markup.php");
  16. if ($_GET['page']=="sect1")
  17. {
  18. str_replace("{TITLE}", "Sezione 1 del mio sito", $page);
  19. str_replace("{CONTENT}", fileRead("sect1.html"), $page);
  20. }
  21. else
  22. {
  23. str_replace("{TITLE}", "Il mio primo sito", $page);
  24. str_replace("{CONTENT}", fileRead("home.html"), $page);
  25. }
  26. echo $page;
  27. ?>

dove markup.html avrà mantenuto una compatezza che ci consentirà una modifica immediata del codice html senza paura di aver dimenticato un tag aperto (o chiuso).

markup.html

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ...
  2. <html xmlns="<a href="http://www.w3.org/1999/xhtml"">http://www.w3.org/1999/xhtml"</a> lang="en">
  3. <head>
  4. <title>{TITLE}</title>
  5. </head>
  6. <body>
  7. <div id="menu"><a href="?page=sect1">Sezione 1</a></div>
  8. <div id="content">
  9. {CONTENT}
  10. </div>
  11. </body>
  12. </html>

Il senso di usare {QUALCOSA} come stringa da sostituire è solo per avere un elemento unico e distinguibile immediatamente alla funzione di ricerca stringhe str_replace().
Questa infatti non è altro che una soluzione idiota ad un problema ben più grosso.

Smarty PHP Templating Library

Smarty è una libreria che mi ha ispirato per realizzare l'esempio precedente, ma consente la realizzazione di logiche complesse, come la possibilità di ripetere porzioni codice di markup, ad es. per realizzare un template per un blog, i cui post si ripetono (come codice HTML) nella pagina: quindi scrivendo un solo blocco e definire degli array su cui iterare.

index.php

  1. <?php // funzioni di vario genere
  2. // inizializzazioni di variabili
  3. // if vari ed eventuali
  4. $smarty_site = new Smarty_Site();
  5. if ($_GET['page']=="sect1")
  6. {
  7. $title = "Sezione 1 del mio sito";
  8. $content = "sect1.html"
  9. }
  10. else
  11. {
  12. $title = "Il mio primo sito";
  13. $content = "home.html";
  14. }
  15. $smarty_site->assign("title", $title);
  16. $smarty_site->assign("content", $content);
  17. $smarty_site->display("markup.tpl");
  18. ?>

Con home.html e sect1.html che rimangono così come li abbiamo definiti, mentre markup.tpl sarà:

smarty/templates/markup.tpl

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ...
  2. <html xmlns="<a href="http://www.w3.org/1999/xhtml"">http://www.w3.org/1999/xhtml"</a> lang="en">
  3. <head>
  4. <title>{$title}</title>
  5. </head>
  6. <body>
  7. <div id="menu"><a href="?page=sect1">Sezione 1</a></div>
  8. <div id="content">
  9. {include file=$content}
  10. </div>
  11. </body>
  12. </html>

Tutti i file saranno messi nella directory templates in base alle direttive della libreria.
Chiaramente l'esempio non sfrutta le potenzialità di Smarty, qundi una buona letta alla sua documentazione potrebbe aiutare!

Usare eval()

Un'altra possibilità di templating che è possibile realizzare con PHP è quella di sfruttare le potenzialità della funzione eval(): questa funzione non fa altro che valutare come codice il contenuto di una stringa. L'esempio è tratto direttamente dal sito di php.net.

  1. <?php
  2. function parseTemplate($template, $params=array()) {
  3. foreach ($params as $k=>$v) {
  4. $$k = $v;
  5. }
  6. ob_start();
  7. eval("?>" . implode("", file($template)) . "<?");
  8. $c = ob_get_contents();
  9. ob_end_flush();
  10. return $c;
  11. }
  12. ?>

Nota: ob_start(), ob_get_contents(), ob_end_flush(), servono per bufferizzare, cioè ritardare l'output che eval produrebbe, consentendoci quindi di salvarlo nella variabile $c che verrà quindi ritornata.
Esempio:

  1. <?php
  2. echo parseTemplate("myTemplate.php", array('account'=>$row));
  3. ?>

e myTemplate.php:

  1. <?php foreach($account as $k=>$v) : ?>
  2. <?=$k?>: <?=$v?>
  3. <? endforeach; ?>

Conclusioni

Col fatto che php è stra-usato, di metodi applicativi ce ne sono a bizzeffe: quello che importa è capire il sistema di funzionamento del programma, quindi come sfruttarlo al meglio. I vari metodi presentati hanno una complessità sempre maggiore, per cui gli ultimi due esempi necessitano un minimo di basi dell'uso degli oggetti (in php4 e php5) e degli array associativi.

Spero non vi siate annoiati ;-)

Torna su

Comments

io non mi sono annoiato, l'ho trovato interessante! ;)

ps: se clicco su "Aggiungi commento" mi si apre un'altra pagina; inoltre, nell'inserimento del commento, non c'è la possibilità di inserire il proprio nome, ma solo un eventuale titolo e il commento stesso... :|

Grazie mille per la segnalazione, ho aggiunto l'obbligo di inserimento nome :)
(sono quelle due o tre cosette che mancano da sistemare ;) )

Ciao,

interessante l'articolo. Se ti appassiona l'argomento anche io avevo fatto una mia classe per separare completamente il layout dalla logica di business: ecco il link.

Logicamente per fare ciò devi eliminare i cicli e via dicendo, ma ritengo che alla fine il gioco valga la candela.

Ciao e buon lavoro. Nicola

Ue.. chi si rilegge!

La separazione del codice di markup e' sempre stata una mia fissa.
Pensa... per un sito tipico su LAMP.

Linguaggio di programmazione PHP che genera linguaggio di Ipertesto, recuperando informazioni con un linguaggio di interrogazione. (SQL). (non menziono neanche Javascript dentro l'html)

Un gran casino... indubbiamente.

Io ho trovato un buon compromesso con ZEND, dopo aver provato Smarty in modo molto superficiale.

Zend Framework e' molto flessibile e dopo un po' che lo si usa tutto diventa piu' semplice. (e gestibile)

Con Zend hai dei controllers che impostano delle view, utilizzando dei Modelli per il DB.

Ti consiglio di dargli un occhio :)
A presto ;)

Ciao

ciao XChris, bello leggerti.
In ogni caso hai ragione, Zend è un framework per PHP molto potente che merita davvero il suo uso.
Potrebbe cmq risultare un pelo troppo per chi desidera fare cose piccoline :)

ottimo suggerimento anyway