Home / Indice sezione
 www.icosaedro.it 

 Organizzare siti WEB per funzioni in PHP

Ultimo aggiornamento: 2008-01-21

In questo articolo descriviamo una metodologia di sviluppo per siti WEB attivi incentrata sulla strutturazione per funzioni e orientata al programmatore invece che al grafico. Questo approccio è particolarmente utile per creare articolate interazioni con l'utente che possono richiedere molte pagine. Per dare concretezza alla trattazione descriveremo gli esempi usando il popolare linguaggio PHP, sebbene gli stessi concetti si possano adattare ad altri linguaggi e sistemi di sviluppo.

Indice

Pagine o funzioni?
Strutturare il sito WEB per funzioni
L'esempio in pratica
Estendere il sito
Sicurezza
Limitazioni
Modularità
Conclusioni

Pagine o funzioni?

L'uso più comune del PHP prevede prima di creare un sito WEB statico, e successivamente di inframmezzare al codice HTML le istruzioni PHP necessarie per riempire le parti dinamiche della pagina. Il risultato tipico di questo modo di operare è che il sorgente della pagina risulta un confuso guazzabuglio difficile da manutenere. Se questo metodo di lavoro può essere adeguato nei casi più semplici, per siti WEB articolati e complessi rapidamente la situazione sfugge di mano.


Struttura tradizionale di un sito WEB attivo. Ogni pagina che costituisce il sito corrisponde a un URL distinto e a un mini-programma che la genera. L'attenzione degli sviluppatori si concentra sul layout grafico delle pagine e il codice sorgente risulta ripetitivo e difficile da manutenere.


Per dare ordine al tutto esistono diverse tecniche. Ad esempio si può uscire dalla logica "un file per ogni pagina WEB" e passare alla logica "una funzione per ogni pagina WEB". Così tutto il sito si riduce a un solo file di programma dove ogni funzione si occupa di generare una pagina. In questo articolo descriviamo questa soluzione.

Strutturare il sito WEB per funzioni

Il trucco è abbastanza semplice: ogni pagina WEB è individuata da un parametro di nome f che riporta il nome della funzione che la deve generare. Ad esempio, per la pagina "home" del sito scriveremo una funzione Home() che sarà individuata dal parametro f=Home. Le altre pagine del sito dovranno essere battezzate con un nome e una rispettiva funzione che le genera. Potremo così avere la pagina Listino(), la pagina Carrello(), ecc. che hanno il significato facilmente intuibile.


WEB-application capace di generare le diverse pagine di cui è composto il sito in base al valore di un parametro contenuto nella richiesta proveniente dal client. Nell'esempio della figura il parametro f=Home determina la chiamata della funzione Home() della WEB-application. Lo sviluppo del sito WEB si concentra sull'aspetto di programmazione.


Il nostro programma, oltre alle varie funzioni/pagine, avrà anche una parte di codice che interpreta il valore del parametro f e che richiama la funzione prevista. A questo scopo basta una semplice istruzione switch.

L'esempio in pratica

Il programma sarà allora costituito da qualche funzione di utilità (Anchor(), Testa(), Piede()), dalle funzioni che generano le varie pagine (Home(), Pagina1(), Pagina2(), ...) e da uno switch che invoca la funzione in base al valore di f. Schematicamente:

$f = $_REQUEST['f'];
switch( $f ){
    case 'Home':    Home();    break;
    case 'Pagina1': Pagina1(); break;
    case 'Pagina2': Pagina2(); break;
    default: Home();
}

Ogni ancora dovrà contenere un parametro di nome f che è il nome della funzione da chiamare. A questo provvede la funzione Anchor(). Questa funzione ha due argomenti fissi: il testo dell'ancora in formato HTML e il nome della funzione da richiamare se quell'ancora viene cliccata. La funzione provvede poi a generare il tag HTML corretto. La funzione Anchor() può anche avere coppie nome/valore di parametri da aggiungere all'URL generato: la funzione provvede a codificarli opportunamente e ad aggiungerli nell'URL. Ad esempio, per produrre un'ancora che richiama la funzione/pagina Tabella() con parametri utente=Mario Rossi e sessione=abcdefgh (il cui significato, qualunque esso sia, per il momento non ci interessa) basterà scrivere:

Anchor("Tabella dati", "Tabella",
    "utente", "Mario Rossi",
    "sessione", "abcdefgh");

che invierà nella pagina WEB il seguente codice (su di una unica riga):

<a href="/esempio1.cgi?f=Tabella&utente=Mario%20Rossi
&sessione=abcdefgh>Tabella dati</a>

Per dimostrare il funzionamento di tutto l'ambaradam, ho preparato anche un esempio funzionante: basta cliccare qui sotto per vedere il programma in funzione e il sorgente completo:

#!/bin/php -c..
<?php
/**
 * Example of WEB application structured by functions.
 * This source program is commented in the article {@link
 * http://www.icosaedro.it/php/web-per-funzioni.html}.  It illustrates how a
 * WEB application might be structured by functions, each one implementing a
 * "page".  The HTML FORMs are not handled, because this is the subject of
 * another article (see <i>Secure Call-Back Handling in WEB Pages</i>).
 * @package WebByFunctions
 * @copyright 2004 by icosaedro.it di Umberto Salsi
 */

/*. require_module 'standard'; .*/


/**
 * Generate a HTML anchor.  The HTML anchor generated, if clicked by the
 * user, will cause the invocation of the given function with the given
 * arguments.
 * @param string $text Text of the anchor that the user will see.
 * @param string $func Name of the function to call if this anchor is clicked
 * by the user (also referred as the "call-forward" in the article).<p>
 * The optional arguments are the HTTP parameters that $func will get
 * through $_REQUEST[] or $_GET[]. These parameters may be numbers
 * or strings. Complex types must be serialized with serialize() and
 * unserialized with unserialize() by $func.
 * @return void
 */
function Anchor($text, $func /*., args.*/ )
{
    echo "<a href=\"" . $_SERVER["PHP_SELF"] . "?f=$func";
    for( $i = 2; $i < func_num_args();  ){
        echo "&", (string) func_get_arg($i), "=",
            urlencode((string) func_get_arg($i+1));
        $i+=2;
    }
    echo "\">", $text, "</a>";
}


/**
 * Send the HTTP header and the HTML header.  This function may be customized
 * to include backgrounds, page borders, styles and other layout elements.
 * @return void
 */
function PageHead()
{
    header("Content-Type: text/html; charset=ISO-8859-15");
    echo "<html><head><title>Esempio 1</title></head><body>";
}


/**
 * Close the HTML code.  The logical complement of PageHead().
 * @return void
 */
function PageFoot()
{
    echo "</body></html>";
}


/**
 * The home page of this WEB application.  This is the page the application
 * will call by default if no other page is specified or an error
 * occurred. Three anchors are set: the first one simply call the function
 * Page1() without arguments; the second one call Page2() with the argument
 * 'x'; the last one shows the source of this program.
 * @return void
 */
function Home()
{
    PageHead();
    echo "<h1>Wellcome in our beautiful WEB site!</h1>";
    echo "Please, select a page:<p>";
    Anchor("Page 1", 'Page1'); echo "<p>";
    Anchor("Page 2", 'Page2', 'x', 'the_value_of_x'); echo "<p>";
    Anchor("See the source", 'SeeSource'); echo "<p>";
    PageFoot();
}


/**
 * Show the source of this program.
 * @return void
 */
function SeeSource()
{
    header("Content-Type: text/plain");
    readfile( basename( $_ENV['SCRIPT_NAME'] ) );
}


/**
 * Simple, little WEB page.
 * @return void
 */
function Page1()
{
    PageHead();
    echo "<h1>Page 1</h1>A simple, little WEB page.";
    PageFoot();
}


/**
 * Acquire the parameter 'x'.  This "page" acquires the parameter 'x'
 * we set inside Home().
 * @return void
 */
function Page2()
{
    PageHead();
    echo "<h1>Page 2</h1>";
    echo "x=", htmlspecialchars( (string) $_REQUEST['x'] );
    PageFoot();
}


/*
    Determinate the page to call.
*/

$f = isset($_REQUEST['f'])? (string) $_REQUEST['f'] : "Home";
switch( $f ){
    case 'Home':    Home();    break;
    case 'SeeSource': SeeSource(); break;
    case 'Page1': Page1(); break;
    case 'Page2': Page2(); break;
    default: Home();
}

Estendere il sito

La procedura da seguire per aggiungere nuove pagine al sito è la seguente:

  1. Battezzare la nuova pagina WEB con un nome. Il nome verrà attribuito alla funzione che genera questa nuova pagina. Per fissare le idee poniamo che questa pagina si chiami NuovaPagina.

  2. Scrivere la funzione NuovaPagina(). E' utile sfruttare le funzioni di utilità Testa() e Piede() per dare al sito un aspetto uniforme, ma questo non è strettamente necessario. Ricordare di generare le ancore sempre usando la funzione Anchor().

  3. Aggiungere una riga alla istruzione switch nel corpo principale del programma:
    case 'NuovaPagina': NuovaPagina(); break;
  4. Inserire l'ancora che richiama questa pagina in qualche altra pagina, per esempio nella Home():
    Anchor('Clicca qui', 'NuovaPagina');
Fatto!

Sicurezza

Tutti i parametri provenienti dal client possono essere artefatti, avere lunghezza arbitraria o mancare del tutto. Non esiste alcuna garanzia che i parametri impostati con la funzione Anchor() verranno ritornati al server intatti. L'utente può comporre nella barra degli indirizzi del suo browser un URL arbitrario con parametri arbitrari e poi richiamare la nostra WEB-application con questi parametri.

Pertanto in ogni funzione/pagina che richiede dei parametri DOBBIAMO controllare che questi parametri ritornati siano validi e coerenti, le stringhe siano codificate nel charset previsto e siano di lunghezza ragionevole, i numeri siano positivi o nulli e non superino un certo massimo, ecc. Inoltre, anche se i dati sono validi presi singolarmente, dovremo verificare che siano validi nel loro insieme: il mese "febbraio" e il giorno "31" sono valori validi presi singolarmente, ma nel complesso individuano un giorno che non esiste. Se il nostro programma accetta ciecamente questi parametri, ecco che avremo creato una falla di sicurezza con conseguenze imprevedibili.

Essere costretti ad affrontare il problema della affidabilità dei parametri della richiesta dentro a ogni pagina/funzione vuol dire anche che la sicurezza del sito tende a calare man mano che il numero di pagine (e quindi di funzioni) aumenta. Inoltre dover verificare la correttezza di parametri che erano già stati a suo tempo determinati dal nostro stesso programma sembra un po' stupido, aumenta la complessità e riduce l'efficienza al crescere del numero dei parametri. In una parola, il nostro metodo di sviluppo scala male al crescere della WEB-application.

Lo stesso problema incombe anche sui siti WEB strutturati nel modo tradizionale, ma da un sistema di programmazione WEB serio è lecito aspettarsi qualcosa di più. Nel prossimo articolo metteremo a punto un meccanismo che risolve completamente questo problema.

Limitazioni

Esistono delle limitazioni alla lunghezze delle richieste GET, sia lato client, sia lato server. Ad esempio, il server Apache dispone della direttiva LimitRequestLine il cui valore default è di 8190 bytes. Questo pone un limite al numero e alla lunghezza degli argomenti delle richieste GET. Anche questa limitazione verrà risolta man mano che perfezioneremo il nostro framework.

Qui ci siamo concentrati sulle richieste di tipo GET coinvolte nelle ancore HTML. Per supportare i FORM potremmo estendere facilmente il nostro mini-framework aggiungendo un campo nascosto "f" con il nome della funzione che acquisisce il FORM; altri campi nascosti potrebbero contenere gli eventuali parametri aggiuntivi. Nel prossimo articolo affronteremo il problema dei FORM in modo più versatile e vedremo come sia possibile produrre FORM con due o più bottoni e come sia possibile associare ad ogni bottone una distinta funzione della WEB-application. Questo ci permetterà di sviluppare maschere di inserimento più ricche e articolate.

Modularità

WEB-application più articolate richiedono di suddividere il programma in più moduli. Una tipica applicazione gestionale potrebbe richiedere i moduli Anagrafica, Fatture, Magazzino, ecc. Il nostro esempio didattico non tiene conto di questa necessità. Ad esempio, come fare per individuare in modo univoco la funzione "Inserimento()" del modulo "Fatture"? Un modo semplice di risolvere questo problema è introdurre una sintassi articolata per il nome della funzione che comprende anche il nome del modulo dove si trova la funzione. Ad esempio, la richiesta proveniente dal client conterrà il parametro f=Inserimento@Fatture dove il modulo da caricare viene indicato dopo la chiocciola.

Per implementare questo meccanismo dovremo cambiare un po' la struttura della WEB-application, in modo che tutte le richieste provenienti dal client vadano al request handler rh.php. Il request handler agisce come centro di smistamento delle richieste: analizza la richiesta proveniente dal client, individua il modulo e la funzione da richiamare, carica dinamicamente il modulo necessario ("Fatture") e infine invoca la funzione di questo modulo ("Inserimento").

Al request handler si possono delegare anche altre utili funzionalità accessorie, come riconoscere e rinnovare la sessione, mantenere un log degli accessi della WEB-application, filtrare le richieste malevole o artefatte, ecc. Vedremo come implementare tutto questo gradatamente nel corso dei prossimi articoli e arriveremo a delineare un sistema completo che ho denominato "bt_" (bee-tee-underscore).

Conclusioni

Abbiamo descritto una implementazione delle ancore HTML. Non abbiamo invece affrontato il problema quando sono coinvolti i FORM. Questo richiederà una ulteriore evoluzione della nostra tecnologia che vedremo nel prossimo articolo (Organizzare siti WEB per funzioni in PHP - Soluzione generale, www.icosaedro.it/php/web-per-funzioni-2.html) e che permetterà di associare ad ogni bottone del FORM una diversa funzione della nostra WEB-application.

Il difetto di questa strategia, come del resto di tutte le soluzioni comunemente utilizzate (ASP e .NET inclusi), è che i dati viaggiano inutilmente avanti e indietro tra client e server. Anche questo si può risolvere, ma occorre fare una evoluzione ulteriore che coinvolge il concetto di sessione e che porta ad una interazione di tipo modale.

La sezione dedicata al PHP (www.icosaedro.it/php) prosegue con altre soluzioni più sofisticate di WEB-application stateful e ad interazione modale che coprono anche i FORM, rimuovono il problema dei parametri di stato che viaggiano avanti e indietro, e incrementano la sicurezza in modo trasparente al programmatore.


Umberto Salsi

Contatto
Mappa
Home / Indice sezione
Still no comments to this page. Use the Comments link above to add your contribute.