venerdì, dicembre 27, 2013

XML, JSON, ecc.

Pubblico questo post per presentare qualche semplice esempio di informazioni inviate da un server web ad un client in diversi formati.

L'esperienza comune degli utenti è l'interazione con un server web mediata da un browser. Il browser agisce come client, chiedendo una risorsa, il server gliela manda. La risorsa, tipicamente, consiste in un documento ipertestuale (una pagina web), oppure un file binario (un'immagine, un file PDF, ecc.).

Se si tratta di un file binario, esso viene presentato all'utente mediante qualche programma di visualizzazione (spesso integrato nel browser come plugin, o direttamente in esso incorporato). Le pagine HTML vengono semplicemente "rese".

Sempre più spesso, però, capita che le informazioni inviate dal server debbano essere elaborate da un'applicazione sul lato client, per i fini più diversi. Non solo rese, ma elaborate: questo significa che il modo in cui le informazioni arrivano sia studiato in modo che i dati siano facilmente elaborabili.

Immaginiamo che un server debba inviare l'elenco dei libri disponibili per il prestito, il cui autore è Manzoni.

Potrebbe inviare un testo semplice, con i titoli uno sotto l'altro:

Content-type: text/plain

Il Conte di Carmagnola
Adelchi
I promessi sposi

Oppure potrebbe inviare un file codificato in XML:

Content-type: text/xml

<?xml version="1.0" encoding="UTF-8"?>
<books>
  <book id="123"><title>Il Conte di Carmagnola</book>
  <book id="456"><title>Adelchi</book>
  <book id="789"><title>I promessi sposi</book>
</books>

Oppure ancora, inviare gli stessi dati in formato JSON:

Content-type: application/json

[{"id":123,"title":"Il Conte di Carmagnola"},{"id":456,"title":"Adelchi"},{"id":789,"title":"I promessi sposi"}]

Ci sono vantaggi e svantaggi nella scelta di una di queste soluzioni. Negli ultimi anni sta prendendo sempre più piede il formato JSON, anche in virtù del fatto che sia la codifica sia la decodifica di strutture dati da / verso questo formato è agevole in tutti i linguaggi di programmazione moderni, sia perché spesso ad elaborare i dati lato client sono applicazioni JavaScript, che trovano i dati disponibili in formato nativo.


lunedì, ottobre 28, 2013

Web application deployment with git-ftp

This post has been updated after its initial publication.

In web application development, it happens sometimes that you are stuck with an only way to upload files on your webserver: FTP. If you are used to push changes to a git server like and then pull the changes on your webserver through a SSH connection, there are chances you just can't do that.

I had this problem recently, but I luckily found a very nice shell, git-ftp, that allows me to push changes to the webserver via FTP, recognizing all the files that I updated (or added, or deleted) on my local git repository.

My development / deployment cycle goes now simply like this:

$ # the ordinary "git" stuff...:
$ git add files_to_add

$ git rm files_to_remove
$ git commit -a -m 'description of the changes'
$ git push

$ # now push the changes on the deployment server:
$ git-ftp push

The credentials that have to be used to access the deployment server are stored with the ordinary git config command. From the man page of git-ftp:

$ git config git-ftp.user john
$ git config git-ftp.url ftp.example.com
$ git config git-ftp.password secr3t
$ git config git-ftp.syncroot path/dir
$ git config git-ftp.cacert caCertStore
$ git config git-ftp.deployedsha1file mySHA1File
$ git config git-ftp.insecure 1

You can also use git-ftp in dry-mode, to just see what would happen, and in verbose mode, if you are curious about what actually happens.

Update: for a different project, I needed to use sftp, which is not supported by libcurl with a default Ubuntu installation. Anyway, I followed the instructions found on zeroset and I managed to have it working with no problems.

domenica, settembre 29, 2013

Treno + bici, assurdità tutte italiane

Viaggiare in treno con la bicicletta al seguito può essere un'esperienza molto piacevole, visto che si possono esplorare luoghi relativamente lontani sfruttando due tra i mezzi di trasporto più ecologici.

Purtroppo, per motivi che solo in Italia credo possano esistere, l'esperienza viene resa inutilmente difficoltosa.

1. Al momento della pianificazione del viaggio, bisogna naturalmente scegliere i treni giusti, ossia quelli che consentono il trasporto della bicicletta. Il sito di Trenitalia non consente, nemmeno accedendo alle opzioni avanzate, di effettuare una ricerca dei treni adibiti al trasporto biciclette:


I ciclisti italiani sanno, grazie ad un salutare passaparola, che per ottenere l'informazione desiderata si può però consultare il sito delle ferrovie tedesche, anche in italiano, il quale mostra gli orari di tutta Europa, con l'agognata casellina su cui mettere il desiderato segno di spunta:


2. Al momento di fare il biglietto, bisogna considerare che, per motivi misteriosi che qualcuno dovrebbe spiegare, le emettitrici automatiche presenti in tutte le stazioni non consentono l'acquisto del supplemento bici. Quindi, armatevi di pazienza e mettetevi in fila allo sportello (se è aperto dovete farlo, visto che la regola di acquistare il biglietto in treno senza sovrapprezzo vale solo quando in stazione lo sportello non c'è oppure è chiuso).

3. L'accessibilità delle stazioni ferroviare italiane (e dei treni stessi, a dire il vero) è quella che è, lo sappiamo. Dappertutto troviamo i cartelli "Vietato attraversare i binari. Servirsi del sottopassaggio". Basta aver fatto un viaggio piccolo piccolo al di là delle Alpi per capire che i sottopassaggi a cui si può accedere solo scendendo e salendo dei gradini non sono il massimo che la vita può offrire (soprattutto se la bicicletta è carica con qualche bagaglio sulle borse). Tra l'altro, l'accessibilità riguarda anche persone che devono usare una sedia a rotelle, genitori con un passeggino, ecc. Ma in Italia, naturalmente, le priorità sono altre.

4. Una volta arrivati al binario, rimane un ultimo problema da affrontare. Dove sarà la carrozza adibita al trasporto delle biciclette? In testa o in coda al treno? Non c'è modo di saperlo, perché evidentemente sarebbe troppo difficile comunicare questa informazione negli annunci audio, o nei display dove sono elencati i treni in arrivo/partenza. Quindi, non rimane che tirare una monetina e sperare di azzeccarci. Se non avete fortuna, e vi siete messi in coda quando invece la carrozza portabici è in testa, dovrete correre velocemente sul binario per arrivare a caricare il vostro mezzo in tempo. Però attenzione: non potete pedalare sul marciapiede.

Insomma: meno male che «Trenitalia da sempre si impegna per favorire chi sceglie di sostenere l’ambiente per viaggiare, forte della convinzione che treno+bici sia la soluzione di viaggio più eco-friendly, oltre che sempre più trendy», come afferma il sito web dell'azienda. Figuratevi altrimenti.


venerdì, agosto 30, 2013

PHP - Introduzione alle lezioni

Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Introduzione

Le lezioni di questo corso sono strutturate con una serie di esempi e di esercizi per tuffarsi nel modo dello sviluppo di applicazioni web con PHP.
L'idea è di non presentare sistematicamente tutto ciò che serve: c'è molta altra documentazione al riguardo, a partire da www.php.net/manual e uno dei principi di base è quello di "non reinventare la ruota". Piuttosto, preferisco procedere per raffinamenti successivi, con refactoring costante delle cose prodotte.
Tenderei anche ad iniziare (quasi) fin da subito un'impostazione basata sull'approccio MVC (model/view/controller) e sul test-driven development, in modo da facilitare il passaggio allo sviluppo basato sull'uso di framework (personalmente, uso e raccomando symfony, ma molti concetti sono applicabili comunque).
La raccolta di esempi e di esercizi presentata qui viene normalmente integrata con spiegazioni in aula, quindi può essere che non tutto sia sempre chiaro se ci si basa solo su quanto letto qui. Se servono integrazioni, o se trovate errori, segnalatemelo.

Alcune buone pratiche

Riporto qui alcune buone pratiche per lo sviluppo di applicazioni web che mi vengono in mente basandomi sulla mia esperienza personale e su qualche buona lettura. Non c'è un ordine di priorità particolare, e probabilmente alcune cose dovranno essere aggiunte. Tra l'altro, ammetto di non riuscire a seguirle tutte neppure io. Ad ogni modo, sono i miei due centesimi...

  1. usare il design pattern MVC (model-view-controller)
  2. sviluppare codice sorgente in inglese: è meglio setSurname() di setCognome(), soprattutto se poi si chiede aiuto in un qualche tipo di forum internazionale
  3. usare strumenti che consentono l'internazionalizzazione (i18n) e la localizzazione (l10n) del codice
  4. essere ben disposti verso il refactoring
  5. usare un sistema di controllo versione (SVN, GIT...)
  6. scrivere il codice seguendo degli standard precisi (vedere, ad esempio, quelli di symfony, oppure quelle del progetto o del team in cui si è coinvolti)
  7. scrivere codice commentato
  8. scrivere codice autoesplicativo (pensare ai nomi delle funzioni e dei parametri)
  9. produrre documentazione con phpDoc o sistemi equivalenti
  10. lavorare con dati di esempio da ricaricare per ogni test (fixtures)
  11. predisporre test unitari
  12. predisporre test funzionali
  13. usare librerie di astrazione dal database
  14. usare librerie ORM (object-relational-mapping)
  15. usare framework di sviluppo per non reinventare la ruota
  16. usare un'interfaccia fluente per gran parte delle funzioni di una classe (tutti i setters, come minimo)
  17. non usare il metodo GET per cambiare lo stato delle informazioni sul server (niente link "normali" per cancellare un articolo, ad esempio)
  18. gestire le eccezioni con blocchi try... catch per tutte le operazioni che potenzialmente possono non andare a buon fine
  19. impostare l'error_reporting a E_ALL, e inizializzare tutte le variabili esplicitamente
  20. seguire le pratiche dell'usabilità, dell'accessibilità e dell'ottimizzazione per i motori di ricerca
Nell'ambito dell'uso di symfony, inoltre, vale sicuramente la pena di dare un'occhiata alle trenta buone pratiche raccomandate da Nicolas Perriault.

Nota per i puristi

In molti casi gli esempi iniziano con un codice come il seguente:

<pre>
<?php
    // istruzioni...

Ovviamente, il codice generato non è HTML valido, poiché mancano molti elementi obbligatori e il marcatore di chiusura </pre>.

Visto che l'obiettivo è di visualizzare semplice testo, però, questa è una soluzione accettabile.
La soluzione teoricamente corretta sarebbe di scrivere, invece:

<?php
  header('Content-Type: text/plain');
  // istruzioni...


Il problema di questa soluzione è che i browser dovrebbero rappresentare il contenuto come testo semplice, ma ce n'è uno, fornito di default con un noto sistema operativo, che non si comporta così, e tenta di rappresentare il testo in altro modo, oppure di aprire il contenuto con altri programmi...

Php - Alcune cose sulle variabili

Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Ambito di validità delle variabili

Esistono diversi ambiti di validità (scope) di una variabile:
  • locale (la variabile è visibile solo nella funzione in cui è definita)
  • globale (la variabile è definita fuori dalla funzione, ma è specificatamente richiamata)
  • superglobale (la variabile è accessibile ovunque senza bisogno di specificazioni
A differenza di altri linguaggi:
  1. le variabili globali non sono visibili, in assenza di specificazione, all'interno di una funzione (come invece succede, ad esempio, in Pascal);
  2. non esiste uno scope per un blocco (come invece succede, ad esempio, in C).

Si consideri il seguente frammento di codice:

ini_set('error_reporting', E_ALL);
function foo()
{
  echo $k;
}
$k=1;
foo();

Quello che si ottiene è:
Notice:  Undefined variable: k in ... on line ...
Se si deve usare la variabile globale all'interno della funzione (anche se non è una buona pratica), ci sono due possibilità.

La prima è di dichiararla esplicitamente come globale:

function foo()
{
  global $k;
  echo $k;
}
$k=1;
foo();

La seconda è di usare l'array globale $GLOBALS:

function foo()
{
  echo $GLOBALS['k'];
}
$k=1;
foo();

Le variabili superglobali

Alcuni array speciali mettono a disposizione di tutte le funzioni valori specifici collegati all'ambiente di esecuzione ($_ENV), ai valori inviati dal browser ($_GET, $_POST, ecc.). In linea di massima, è bene evitare di accedere direttamente a queste variabili e utilizzare invece delle classi che si occupano di fornire i dati in forma "controllata".

Ad esempio, si consideri di usare un codice simile a questo:

class WebRequest
{
  public function getParameter($name, $default=null)
  {
    if (array_key_exists($name, $_GET))
    {
      return htmlentities($_GET[$name]);
      // eventualmente qui si possono aggiungere altre sostituzioni...
    }
    else
    {
      return $default;
    }
  }
}

$request = new WebRequest();
echo 'con pulizia: ' . $request->getParameter('bar') . "\n";
echo 'senza pulizia: ' . $_GET['bar'] . "\n";

Richiamando il codice con una query string di questo tipo:

?bar=5>3

si otterrà:

con pulizia: 5&gt;3
senza pulizia: 5>3

con evidenti vantaggi in termini di sicurezza (e con il bonus di poter specificare dei valori di default).

Nota storica: in vecchie versioni di PHP (precedenti alla versione 4.2.0), l'impostazione predefinita (register_globals=on) faceva sì che venissero automaticamente create delle variabili con il nome del parametro ($bar nell'esempio). Questa impostazione è deprecata per motivi di sicurezza, ma è bene conoscerla per il caso in cui ci si debba cimentare nella manutenzione/sostituzione di vecchio software.

Variabili statiche

Una funzione può contenere variabili statiche:

function baz()
{
  static $number;
  $number++;
  return 'foo ' . $number . "\n";
}
echo baz();
echo baz();
echo baz();

produce
foo 1
foo 2
foo 3

Variabili di variabili

Una variabile può contenere il nome di un'altra variabile:

$name="Mario";
$var='name';
echo $$var . "\n"; 
produce

Mario

Nota: il meccanismo funziona anche con diversi livelli di indirezione.

La funzione eval()

Con la funzione eval() si possono eseguire istruzioni costruite al volo:

$expression='$a=5+3;';
eval($expression);
echo $a . "\n";

Attenzione: è bene non abusare di queste cose, che minano la sicurezza del codice e ne mettono a rischio la portabilità. «Se eval() è la risposta, vuol dire che quasi certamente hai posto la domanda sbagliata.» (attribuita a Rasmus Lerdorf)

PHP - Appunti sulla progettazione


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Fasi di progettazione

Le fasi di progettazione di una base di dati e di una o più applicazioni utente che su di essa si basano dovrebbero essere le seguenti:
  • progettazione logica, che porta ad un diagramma entità/relazioni
  • progettazione delle tabelle, che porta allo schema relazionale
  • implementazione concreta, che porta alle istruzioni DDL (o equivalenti) per la progettazione delle tabelle con i relativi campi

Consigli per la progettazione delle tabelle

Mantenere la coerenza nei nomi

Bisognerà adottare uno schema per i nomi delle tabelle e dei nomi dei campi ed applicarlo in maniera coerente. Ad esempio, non sembrano coerenti questi nomi di tabelle:
  • Clientifornitori (o scegliamo Clienti e Fornitori, oppure clienti e fornitori)
  • ClientiFornitoriFattura (per i nomi delle tabelle usiamo sempre il plurale o sempre il singolare)
  • CustomersFornitoriInvoices (usiamo nomi in italiano o in inglese, non mescolati)
  • customer_typesubjectTypeBookType (underscore, camelCase o tutte le iniziali in maiuscolo)

Non andare in cerca di guai

In generale, è opportuno limitarsi a nomi di tabelle e nomi di campi:
  • senza spazi
  • senza caratteri fuori dall'insieme [A-Za-z0-9], più eventualmente il simbolo di sottolineatura

Usare dati di prova

Per verificare di aver progettato correttamente le tabelle, è opportuno inserire dei dati di prova:
  • significativi (ad esempio, tenendo in considerazione ipotetici casi limite, o valori fuori dal comune)
  • in numero adeguato (se voglio verificare come si presenta l'interfaccia utente quando ci sono tanti clienti, è bene che nei dati di prova ci siano tanti clienti; viene naturale pensare anche ad una generazione automatica, con numeri progressivi )
  • facili da importare (per poter ripristinare una situazione iniziale, nel caso in cui si commettano degli errori).

PHP - PUT e DELETE tramite Javascript


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Limiti dei browser

Normalmente i browser implementano solo i metodi GET e POST. Se si prepara un form con l'attributo method che non vale "GET" o "POST" il browser fa una richiesta tramite GET.

Utilizzare Javascript per PUT e DELETE

Se si vuole fare un PUT o un DELETE si può però chiedere al browser di utilizzare Javascript. Questo codice, con un po' di adattamenti, è preso da "Not just Post and Get".

<html>
<title>HTTP Methods example</title>
<head>
<script language="Javascript">
<!--
var xmlhttp;
function execute($method,$url)
{
  xmlhttp=new XMLHttpRequest();
  xmlhttp.open($method,$url,true)
  xmlhttp.send(null);
}

function testmethod($method)
{
  alert('Executing ' + $method + '...');
  execute($method, 'info.php');
}
-->
</script>
</head>
<body>
<?php foreach(array('get', 'post', 'put', 'delete') as $method): ?>
    <input 
        type="button" 
        value="<?php echo $method ?>" 
        onclick="testmethod('<?php echo $method ?>');" 
    />
<?php endforeach ?>
</body>
</html>

Il codice HTML che si ottiene per il body è il seguente:
<body>
 <input 
  type="button" 
  value="get" 
  onclick="testmethod('get');" 
 />
 <input 
  type="button" 
  value="post" 
  onclick="testmethod('post');" 
 />
 <input 
  type="button" 
  value="put" 
  onclick="testmethod('put');" 
 />
 <input 
  type="button" 
  value="delete" 
  onclick="testmethod('delete');" 
 />
</body>
Lo script PHP che riceve i dati può limitarsi, giusto per darci la conferma che i metodi PUT o DELETE sono stati ricevuti, a scrivere una specie di file di log:

<?php
// file info.php
$fp= fopen('log.txt', 'a');
fwrite($fp, $_SERVER['REQUEST_METHOD']. "\n");
fclose($fp);
?>

PHP - XAMPP


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

XAMPP

Per esercitazioni sullo sviluppo web in ambiente Microsoft Windows può essere utile usare XAMPP. Consiglio di scaricare e usare la versione indicata come "Metodo B: 'Installazione' senza l'installer"
Dopo aver scompattato il file (ad esempio, in C:\), eseguire lo script setup_xampp.bat per effettuare i cambiamenti necessari a seconda del percorso specificato. Al termine, eseguire xampp_control.exe e tentare di avviare il server Apache.

Problemi e soluzioni

Porta 80 occupata

Ce ne possiamo rendere conto eseguendo xampp-portcheck.exe.
Se è in esecuzione Ms Internet Information Server, che occupa la porta 80, non è possibile avviare normalmente Apache.

Soluzione 1: chiudere IIS, con il comando
net stop IISADMIN /y

Soluzione 2: impostare Apache perché ascolti su una porta diversa (ad esempio la porta 8080), modificando il fileapache/conf/httpd.conf (bisogna trovare la riga con scritto Listen 80 e sostituirla con Listen 8080).

Firewall in azione

All'avvio di Apache potrebbe comparire un messaggio che dice che il Firewall di Windows blocca il servizio.

Soluzione 1: disattivare il firewall, oppure consentire un'eccezione.
Soluzione 2: lavorare solo da localhost, che dovrebbe comunque rimanere possibile.

PHP - Come procedere


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Alcuni argomenti che meriterebbero di essere trattati

Uno sviluppatore PHP dovrebbe avere una buona idea di come sfruttare alcuni strumenti potenti e flessibili che il linguaggio mette a disposizione. Ne elenco alcuni, senza un particolare ordine di priorità:
Ovviamente, a seconda di interessi e necessità concrete, ci si potrà trovare ad avere bisogno di molte altre cose.
L'idea fondamentale è che uno sviluppatore web non dovrebbe reinventare la ruota, ma controllare se esistono soluzioni che può sfruttare (possibilmente ricambiando con proprio codice e/o documentazione).

Documentazione

Il codice può essere facilmente documentato, anche in assenza di commenti formattati in maniera particolare. Lo strumento di riferimento è phpDocumentor.
Se poi il codice viene commentato adeguatamente, l'utilità è ancora maggiore.

ORM e Framework

Per qualsiasi progetto non banale, vale la pena di sfruttare qualche framework di sviluppo, che spesso è appoggiato a un Object-Relational Mapper.
Vale la pena anche di dare un'occhiata a cosa offre il framework Zend.


PHP - Il controllore


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Un unico punto di accesso

Una buona pratica è quella di avere un unico punto di accesso all'applicazione, da usare per tutte le azioni che possono essere compiute dall'utente.
Questo punto di accesso sarà in cui verrà invocato il livello controller (nell'ambito del pattern MVC). Ciò comporta una serie di vantaggi:
  1. centralizzazione dei controlli di validità della richiesta, in base a tutti i parametri ritenuti necessari
  2. generazione di una sola istanza di classi per l'accesso alla base di dati, agli "oggetti" che rappresentano la connessione HTTP (es. WebRequest e WebResponse, se li definiamo, ecc.)
  3. possibilità di abilitare/disabilitare agevolmente l'intera applicazione
  4. possibilità di definire URL più significativi e Search-Engine Optimized.

Definizione degli URL

Potremo definire un unico controller index.php che si occupa di "smistare il traffico" a seconda dell'azione richiesta. In prima approssimazione, gli URL saranno simili ai seguenti:

http://www.example.com/myapp/index.php?action=listitems
http://www.example.com/myapp/index.php?action=showitem&id=12

Con il primo invocheremo l'azione listitems, con il secondo l'azione showitem, passando il parametro id=12 da utilizzare per decidere quale elemento mostrare.

Se le azioni listshowedit, ecc. sono le medesime per diversi tipi di oggetti (es. foto, documenti, video, ecc.) potrà essere conveniente avere azioni in qualche modo raggruppate, così:

http://www.example.com/myapp/index.php?module=photos&action=list
http://www.example.com/myapp/index.php?module=photos&action=show&id=12
http://www.example.com/myapp/index.php?module=videos&action=list
http://www.example.com/myapp/index.php?module=videos&action=show&id=32

Se poi volessimo degli URL più significativi e SEO, potremo fare in modo che il nostro controller effettui un'analisi della query string in modo da poter gestire URL fatti in questo modo:

http://www.example.com/myapp/index.php/photos/list
http://www.example.com/myapp/index.php/photos/show/id/12
http://www.example.com/myapp/index.php/videos/list
http://www.example.com/myapp/index.php/videos/show/id/32

Progettazione degli URL

Gli URL sono una parte importante di un'applicazione web, troppo spesso sottovalutata. Essi dovrebbero essere (vedi i suggerimenti riportati su NewMediaJournalism):
  • permanenti
  • leggibili
  • gerarchici
  • brevi e chiari

Slug al posto dell'id

In molti contesti, è preferibile indicare un elemento con un identificativo mnemonico significativo anziché con uno sterile identificativo numerico progressivo. Questo identificativo, nel caso di articoli giornalistici (ma il concetto può essere esteso ai post di un blog, al titolo assegnato ad un'immagine, ecc.) viene chiamato slug.

Il nome slug identifica un proiettile la cui forma è cambiata a causa del passaggio per la canna della pistola; analogamente, un titolo di un pezzo giornalistico come "Nuove idee per la Wikipedia" potrebbe venire trasformato, anche con procedimenti automatici, nello slug "nuove-idee-per-la-wikipedia".

URL rewriting

Tramite opportuna configurazione del server web, e agendo sul file .htaccess, è possibile evitare che nell'URL compaia un riferimento allo script invocato (in pratica viene reso invisibile il nome index.php):

http://www.example.com/myapp/photos/list
http://www.example.com/myapp/photos/show/id/12
http://www.example.com/myapp/videos/list
http://www.example.com/myapp/videos/show/id/32

Ulteriori informazioni in merito si possono trovare nella guida all'URL-rewriting del sito HTML.it.

Esercizio

Mettere insieme i pezzi visti fino a qui, e progettare una piccola applicazione con separazione netta dei livelli MVC, definendo le classi appropriate, progettando gli URL, predisponendo i test necessari, ecc.

PHP - Modelli (template)


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Livello "vista" tramite modelli (template)

Lo sviluppo che segue la modalità nota come MVC prevede una netta separazione tra modello (accesso ai dati), controllore(autenticazione, gestione del chi può fare cosa, ecc.) e vista (forma di presentazione dei dati).
In linea di massima, il modello e il controllo devono limitarsi a estrarre i dati e a fornirli alla vista in forma di semplici variabili (array e oggetti), già pronti per essere rappresentati.

Nella vista, il fatto che i dati derivino da interrogazione di database, da analisi di file XML, o da qualsiasi altra fonte, è assolutamente ininfluente.

Analogamente, il modello non ha idea dello scopo finale per cui vengono richiesti ed estratti dei dati, e li fornirà in maniera il più possibile grezza.

Il controllore ha lo scopo di capire quale interrogazione deve essere fatta e quale forma di visualizzazione adottare. Ad esempio, vengono chiesti i dati sugli articoli che possono essere visualizzati in forma di:
  • pagina web tabellare
  • pagina web con elenco puntato
  • file CSV
  • documento XML
  • ...

La vista si occupa di formattare i dati nella maniera opportuna. In genere, si predispongono dei template (modelli) e il controllore deciderà quale template utilizzare.

Template in PHP (nativi)

Il modo pìù semplice per prediporre dei template è di usare direttamente PHP.

Ad esempio, il file principale (controller) effettuerà le operazioni di estrazione dei dati e richiamerà un template per la visualizzazione.

<?php
$name='George Smith';
$address= '45th & Harris';
include('template.php');

Il file template.php conterrà codice HTML e codice PHP con sintassi alternativa:

<html>
<head>
<title>User Info</title>
</head>
<body>
<p>User Information:<br />
Name: <?php echo $name ?><br />
Address: <?php echo $address ?>
</p>

</body>
</html>

Il risultato sarà, come ci aspettiamo:

User Information: Name: George Smith
Address: 45th & Harris

Vantaggi:
  • non serve imparare un nuovo "linguaggio"
  • non serve installare nessuna libreria di funzioni
  • nel template si possono usare tutte le funzioni di PHP per la formattazione dei dati, non ci sono limiti
Svantaggi:
  • chi prepara il template deve conoscere (almeno un minimo di) PHP
  • i template non sono portabili tra ambienti di sviluppo diversi
  • i template risultano prolissi e poco comprensibili
  • chi prepara il template può comunque eseguire codice PHP "normale" (con eventuali problemi di sicurezza)


Template con codice specifico

In alternativa, si possono usare strumenti per preparare template indipendenti da PHP. Il codice di questi template in genere viene analizzato e tradotto in codice PHP nativo, con la gestione di una cache per evitare di dover fare questa operazione più volte.
Il linguaggio per template più noto è probabilmente quello predisposto per Smarty. Per avere un'idea del suo funzionamento, si può dare un'occhiata al suo crash course. Ci sono però oltre cinquanta progetti per i template di pagine web, non c'è che l'imbarazzo della scelta. È presente anche una tabella di comparazione nella Wikipedia.

Un esempio di codice è il seguente (basato sul crash course, con l'aggiunta dell'indicazione dei percorsi per le directory e la libreria):

<?php
require_once('../../../lib/Smarty/libs/Smarty.class.php');
// ovviamente il percorso varia a seconda dell'installazione...

$smarty = new Smarty;

$smarty->template_dir = './smarty_dirs/templates';
$smarty->compile_dir = './smarty_dirs/templates_c';
$smarty->cache_dir = './smarty_dirs/cache';
$smarty->config_dir = './smarty_dirs/configs';

$smarty->assign('name', 'george smith');
$smarty->assign('address', '45th & Harris');

$smarty->display('index.tpl');

Il template (file index.tpl) corrisponderà al seguente:

<html>
<head>
<title>User Info</title>
</head>
<body>
<p>User Information:<br />
Name: {$name}<br />
Address: {$address}
</p>

</body>
</html>

Il risultato corrisponde a quello precedente.

Smarty: un esempio con un array

Se al template Smarty passiamo un array, possiamo sfruttare il codice per la gestione dell'iterazione.

Il controller sarà simile al seguente:

<?php
require_once('../../../lib/Smarty/libs/Smarty.class.php');

$smarty = new Smarty;

$smarty->template_dir = './smarty_dirs/templates';
$smarty->compile_dir = './smarty_dirs/templates_c';
$smarty->cache_dir = './smarty_dirs/cache';
$smarty->config_dir = './smarty_dirs/configs';

$pictures=array(
    'mountain.jpg',
    'desert.jpg',
    'sea.jpg',
    'lake.jpg',
    'countryside.jpg',
    );

$smarty->assign('title', 'My pictures');
$smarty->assign('pictures', $pictures);

$smarty->display('smarty2_basic.tpl');

Il template sarà invece fatto così:

<html>
<head>
<title>{$title}</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h1>{$title}</h1>

<h2>Elenco puntato</h2>

<ul>
{foreach from=$pictures item=picture}
<li>{$picture}</li>
{/foreach}
</ul>

<h2>Testo preformattato</h2>

<pre>
{foreach from=$pictures item=picture}
{$picture}
{/foreach}
</pre>

<h2>Tabella</h2>

<table class="sample">
<tr><th>name</th></tr>
{foreach from=$pictures item=picture}
<tr>
<td>{$picture}</td>
</tr>
{/foreach}
</table>

<h2>Tabella con numero di iterazione</h2>

<table class="sample">
<tr><th>number</th><th>name</th></tr>
{foreach name=picturestable from=$pictures item=picture}
<tr>
<td>{$smarty.foreach.picturestable.iteration}</td>
<td>{$picture}</td>
</tr>
{/foreach}
</table>

<h2>Tabella con colori di sfondo diversi per le righe pari e dispari</h2>

<table class="sample">
<tr><th>name</th></tr>
{foreach name=picturestable from=$pictures item=picture}
<tr>
<td style="background-color: {if $smarty.foreach.picturestable.iteration is even}yellow{else}orange{/if}">{$picture}</td>
</tr>
{/foreach}
</table>

</body>
</html>




Header/Footer o Layout?

Nella predisposizione dei template, si possono usare due strategie: quella di predisporre intestazioni e piè di pagina standard (e di occuparsi dei contenuti) oppure quella di impostare un layout complessivo, in cui è presente un segnaposto per il contenuto specifico.

Header/Footer

Templating basato su Header/Footer (immagine tratta da Practical Symfony)

L'idea di base è che nel template devo incorporare l'intestazione e il piè di pagina. Nella logica di Smarty dovrò scrivere qualcosa tipo:

{include file="header.tpl" title="Main Menu"}
{* qui va il corpo del template *}
{include file="footer.tpl" logo="http://my.example.com/logo.gif"}

Layout

Templating basato su Layout (immagine tratta da Practical Symfony)

L'idea qui è che ci sia un file di layout generico che contiene tutto il codice di base, ad eccezione di quanto comparirà di specifico a seconda dell'azione invocata. 
Nella logica di Smarty avrò un file layout.tpl che contiene il layout generico:

<html>
<head>
<title>{$title}</title>
</head>
<body>
<h1>{$title}</h1>
<div id="content">
{include file="$action.tpl"}
</div>
</body>
</html>

Il file di layout includerà un file specifico a seconda dell'azione indicata dal controller, che avrà la seguente forma:

<?php
require_once('../../../lib/Smarty/libs/Smarty.class.php');
$smarty = new Smarty;

$smarty->template_dir = './smarty_dirs/templates';
$smarty->compile_dir = './smarty_dirs/templates_c';
$smarty->cache_dir = './smarty_dirs/cache';
$smarty->config_dir = './smarty_dirs/configs';

$pictures=array(
    'mountain.jpg',
    'desert.jpg',
    'sea.jpg',
    'lake.jpg',
    'countryside.jpg',
    );

$smarty->assign('title', 'My pictures');
$smarty->assign('pictures', $pictures);
$smarty->assign('action', 'list');

$smarty->display('layout.tpl');
 
Il file list.tpl sarà quello che caratterizza l'azione specifica:

<h2>The list</h2>
<ul>
{foreach from=$pictures item=picture}
<li>{$picture}</li>
{/foreach}
</ul>

Approfondimenti

Nella predisposizione di modelli (template), è bene tenere presente alcuni concetti che possono tornare utili. Qui userò la terminologia usata con Symfony (altri framework potrebbero usare termini diversi per gli stessi oggetti).

Slot

È una parte del layout il cui valore viene deciso nel momento in cui viene predisposto il contenuto del template relativo all'azione specifica. L'esempio comune è quello del testo del titolo della pagina.

Partial

È una sorta di mini-template che serve per rappresentare una parte dei contenuti e che non ha vita autonoma (non viene richiamato direttamente, almeno di solito, ma da un altro template). È utile per poter sfruttare lo stesso codice in diversi template (un po' come si farebbe con una funzione).

Component

È come un template, con la differenza che usa un'azione specifica e coinvolge quindi il livello del controller.

Flash

È una parte di pagina web che contiene un testo che viene visualizzato una sola volta (al successivo GET scompare), grazie all'uso di cookies. Tipicamente viene utilizzato per dare feedback all'utente sulla riuscita o meno di operazioni che richiedevano l'uso di un POST (visto che il browser viene ridiretto ad un GET).

Helper

Funzioni specifiche che agevolano la preparazione di template (ne esistono per rappresentare date, paesi, misure, messaggi internazionalizzati, ecc.).