Skip to content

3. Un controller generico

3.1. Introduzione

Nel metodo precedente, era sottinteso che dovessimo scrivere il controller denominato main.php. Con un po’ di esperienza, ci si rende conto che questo controller svolge spesso le stesse operazioni ed è quindi allettante scrivere un controller generico utilizzabile nella maggior parte delle applicazioni web. Il codice di questo controller potrebbe essere il seguente:

<?php
     // controller generico

  // lettura della configurazione
  include 'config.php';

  // inclusione di librerie
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

  // si avvia o si riprende la sessione
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

  // si recupera l'azione da intraprendere
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // la sequenza delle azioni è normale?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // sequenza anomala
    $sAction='enchainementinvalide';
  }//if

     // elaborazione dell'azione
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

  // invio della risposta (vista) al cliente
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

  // fine dello script - non si dovrebbe arrivare a questo punto, a meno che non ci sia un bug
  trace ("Erreur de configuration.");
  trace("Action=[$sAction]");
  trace("scriptAction=[$scriptAction]");
  trace("Etat=[$sEtat]");
  trace("scriptVue=[$scriptVue]");
  trace ("Vérifiez que les script existent et que le script [$scriptVue] se termine par l'appel à finSession.");
  exit(0);

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: dizionario di configurazione
      // $dSession: dizionario contenente le informazioni di sessione
         // $dReponse: il dizionario degli argomenti della pagina di risposta

    // registrazione della sessione
    if(isset($dSession)){
      //: inserimento dei parametri della richiesta nella sessione
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // nessuna sessione
      session_destroy();
    }

         // visualizzazione della risposta
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // fine dello script
    exit(0);
  }//fine sessione      

  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // verifica se l'azione corrente è autorizzata rispetto allo stato precedente
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // verifica dell'azione
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

  //--------------------------------------------------------------------
  function dump($dInfos){
      // visualizza un dizionario di informazioni
    while(list($clé,$valeur)=each($dInfos)){
        echo "[$clé,$valeur]<br>\n";
    }//while
  }//monitoraggio

  //--------------------------------------------------------------------
  function trace($msg){
      echo $msg."<br>\n";
  }//monitoraggio

?>

3.2. Il file di configurazione dell'applicazione

L'applicazione è configurata in uno script che deve necessariamente avere il nome config.php. I parametri dell'applicazione sono inseriti in un dizionario denominato $dConfig, utilizzato sia dal controller che dagli script di azione, dai modelli e dalle viste elementari.

3.3. Le librerie da includere nel controller

Le librerie da includere nel codice del controller sono riportate nella tabella $dConfig['includes']. Il controller le include con la seguente sequenza di codice:

<?php
...
  // lettura configurazione
  include "config.php";

  // inclusione di librerie
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

3.4. Gestione delle sessioni

Il controller generico gestisce automaticamente una sessione. Salva e recupera il contenuto di una sessione tramite il dizionario $dSession. Questo dizionario può contenere oggetti che devono essere serializzati per poter essere recuperati correttamente in seguito. La chiave associata a questo dizionario è 'session'. Pertanto, il recupero di una sessione avviene con il seguente codice:

<?php

  // avvio o ripresa della sessione
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

Se un'azione desidera memorizzare informazioni nella sessione, aggiungerà chiavi e valori nel dizionario $dSession. Poiché tutte le azioni condividono la stessa sessione, esiste il rischio di conflitti tra le chiavi di sessione se l'applicazione viene sviluppata in modo indipendente da più persone. Si tratta di una difficoltà. È necessario sviluppare un repository che elenchi le chiavi di sessione, un repository condiviso da tutti. Vedremo che ogni azione termina con la chiamata alla seguente funzione finSession:

<?php
... 
 // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: dizionario di configurazione
      // $dSession: dizionario contenente le informazioni di sessione
         // $dReponse: il dizionario degli argomenti della pagina di risposta

    // registrazione della sessione
    if(isset($dSession)){
      //: inserimento dei parametri della richiesta nella sessione
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // nessuna sessione
      session_destroy();
    }

         // visualizzazione della risposta
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // fine dello script
    exit(0);
  }//fine sessione      

Un'azione può decidere di non proseguire una sessione. A tal fine è sufficiente non passare alcun valore al parametro $dSession della funzione finSession; in tal caso la sessione viene eliminata (session_destroy). Se il dizionario $dSession esiste, viene salvato nella sessione, che viene poi registrata (session_write_close). L'azione in corso può quindi memorizzare elementi nella sessione aggiungendo voci al dizionario $dSession. Si noti che il controller memorizza automaticamente i parametri della richiesta corrente nella sessione. Ciò consentirà di recuperarli, se necessario, per elaborare la richiesta successiva.

3.5. L'invio della risposta al cliente

Lo scopo finale della funzione finSession è quello di inviare una risposta all’utente. Abbiamo detto che una risposta può avere diversi modelli di pagina. Questi vengono impostati tramite configurazione in $dConfig['vuesReponse']. In un’applicazione con due modelli, si potrebbero quindi avere:

<?php

  $dConfig['vuesReponse']['modele1']=array('url'=>'m-modele1.php');
  $dConfig['vuesReponse']['modele2']=array('url'=>'m-modele2.php');

L'azione in corso specifica in $dReponse['vuereponse'] il modello desiderato. Questo viene visualizzato dal controller tramite l'istruzione:

<?php

         // si visualizza la risposta
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

Una volta inviata questa risposta al client, il controller termina l'esecuzione (exit).

3.6. L'esecuzione delle azioni

Il controller attende richieste contenenti il parametro action=XX. Se tale parametro non è presente nella richiesta e questa è di tipo GET, l'azione assume il valore 'init'. È il caso della primissima richiesta inviata al controller, che ha la forma http://machine:port/chemin/main.php.

<?php
..
  // si recupera l'azione da intraprendere
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';

Per impostazione predefinita, a ogni azione è associato uno script incaricato di elaborarla. Ad esempio:

<?php
... 
// configurazione delle azioni dell'applicazione
  $dConfig['actions']['get:init']=array('url'=>'a-init.php');  
  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

Sono predefinite due azioni:

enchainementInvalide
nel caso in cui l'azione in corso non possa seguire quella precedente
actionInvalide
nel caso in cui l'azione richiesta non esista nel dizionario delle azioni

Le azioni specifiche dell'applicazione sono indicate nella forma metodo:azione, dove metodo è il metodo GET o POST della richiesta e azione è l'azione richiesta, in questo caso: init, calculerimpot, retourformulaire, effacerformulaire. Si noti che l’azione viene recuperata, indipendentemente dal metodo di invio dei parametri (GET o POST), tramite la sequenza:

<?php

  // si recupera l'azione da intraprendere
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init'; 

Infatti, anche se un modulo viene inviato tramite POST, è comunque possibile scrivere:

<form method='post' action='main.php?action=calculerimpot'>
..
</form>

Gli elementi del modulo verranno inviati (method='post'). Tuttavia, l'URL richiesto sarà main.php?action=calculerimpot. I parametri di questo URL verranno recuperati nel dizionario $_GET, mentre gli altri elementi del modulo verranno recuperati nel dizionario $_POST.

Una volta ottenuto il dizionario delle azioni, il controller esegue l’azione richiesta nel modo seguente:

<?php
...
    // elaborazione dell'azione
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

Se l'azione richiesta non è presente nel dizionario delle azioni, verrà eseguito lo script corrispondente a un'azione non valida. Una volta caricato lo script dell'azione all'interno del controller, questo viene eseguito. Si noti che esso ha accesso alle variabili del controller ($dConfig, $dSession) nonché ai dizionari super-globali di PHP ($_GET, $_POST, $_SERVER, $_ENV, $_SESSION). Nello script si trovano sia la logica applicativa che le chiamate alle classi di business. In ogni caso, l’azione dovrà

  • compilare il dizionario $dSession se alcuni elementi devono essere salvati nella sessione corrente
  • indicare in $dReponse['vuereponse'] il nome del modello di risposta da visualizzare
  • terminare con la chiamata a finSession($dConfig, $dReponse, $dSession). Se la sessione deve essere chiusa, l'azione terminerà semplicemente con la chiamata a finSession($dConfig, $dReponse).

Per motivi di coerenza, l'azione potrà inserire nel dizionario $dReponse tutte le informazioni necessarie alle viste. Ma non vi è alcun obbligo. È indispensabile solo il valore $dReponse['vuereponse']. Si noti che ogni script di azione termina con la chiamata alla funzione finSession, che a sua volta termina con un'operazione exit. Non è quindi possibile tornare indietro da uno script di azione.

3.7. La sequenza delle azioni

È possibile considerare un’applicazione web come un automa a stati finiti. I diversi stati dell’applicazione sono associati alle viste presentate all’utente. Quest’ultimo, tramite un link o un pulsante, passerà a un’altra vista. L’applicazione web ha cambiato stato. Abbiamo visto che un'azione viene avviata da una richiesta del tipo http://machine:port/chemin/main.php?action=XX. Questo URL deve provenire da un link contenuto nella vista presentata all'utente. Si vuole infatti evitare che un utente digiti direttamente URL http://machine:port/chemin/main.php?action=XX, bypassando così il percorso che l’applicazione ha previsto per lui. Ciò vale anche se il cliente è un programma.

Una sequenza sarà corretta se la URL richiesta è una URL che può essere richiesta a partire dall’ultima vista presentata all’utente. L’elenco di queste è semplice da determinare. È costituito

  • delle URL contenute nella vista, sia sotto forma di link sia sotto forma di destinazioni di azioni di tipo «submit»
  • URL, che un utente è autorizzato a digitare direttamente nel proprio browser quando gli viene presentata la vista.

L'elenco degli stati dell'applicazione non coincide necessariamente con quello delle viste. Consideriamo ad esempio la seguente vista elementare erreurs.php:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($dReponse["erreurs"]);$i++){
            echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
        }//for
    ?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<a href="<?php echo $dReponse["href"] ?>"><?php echo $dReponse["lien"] ?></a>

Questa vista elementare verrà integrata in una composizione di viste elementari che costituirà la risposta. In questa vista è presente un link che può essere posizionato dinamicamente. La vista erreurs.php potrà quindi essere visualizzata con n collegamenti diversi a seconda delle circostanze. Ciò darà origine a n stati diversi per l’applicazione. Nello stato n. i, la vista erreurs.php verrà visualizzata con il collegamento lieni. In questo stato, è consentito solo l’utilizzo di lieni.

L’elenco degli stati di un’applicazione e delle azioni possibili in ciascuno stato sarà registrato nel dizionario $dConfig['etats']:

<?php
...  
// configurazione degli stati dell'applicazione
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire2.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs2.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

L'applicazione sopra riportata presenta due stati denominati: e-modulo ed e-errori. Si aggiunge uno stato denominato senza-stato che corrisponde all'avvio iniziale dell'applicazione quando questa non presentava alcuno stato. In uno stato E, l'elenco delle azioni consentite si trova nella tabella $dConfig['etats'][E]['actionsautorisees']. Qui vengono specificati il metodo (GET/POST) consentito per l’azione e il nome della stessa. Nell’esempio sopra riportato, sono presenti quattro azioni possibili: get:init, post:alculerimpot, get:retourformulaire e post:effacerformulaire.

Grazie al dizionario $dConfig['etats'], il controller può stabilire se l’azione $sAction in corso sia autorizzata o meno nello stato attuale dell’applicazione. Questo stato viene generato da ogni azione e memorizzato nella sessione in $dSession['etat']. Il codice del controller per verificare se l’azione corrente è autorizzata o meno è il seguente:

<?php
.....
     // La sequenza delle azioni è normale?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // sequenza anomala
    $sAction='enchainementinvalide';
  }//if

     // elaborazione dell'azione
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;
..........
  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // verifica se l'azione corrente è consentita rispetto allo stato precedente
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // verifica dell'azione
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

La logica è la seguente: un'azione $sAction è autorizzata se è presente nell'elenco $dConfig['etats'][$etat]['actionsautorisees'] oppure se tale elenco non esiste autorizzando così qualsiasi azione. $etat è lo stato dell’applicazione al termine del precedente ciclo richiesta client/risposta server. Questo stato è stato memorizzato nella sessione e viene recuperato da lì. Se si rileva che l’azione richiesta non è consentita, viene eseguito lo script $dConfig['actions']['enchainementInvalide']['url']. Questo script provvederà a inviare una risposta adeguata al cliente.

In fase di sviluppo, è possibile non compilare il dizionario $dConfig['etats']. In tal caso, ogni stato autorizza qualsiasi azione. Il dizionario potrà essere messo a punto una volta che l'applicazione sarà stata completamente sottoposta a debug. Esso proteggerà l'applicazione da azioni non autorizzate.

3.8. Debug

Il controller offre due funzioni di debug:

  • la funzione trace consente di visualizzare un messaggio nel flusso HTML
  • la funzione dump consente di visualizzare il contenuto di un dizionario nello stesso flusso

Qualsiasi script di azione potrà utilizzare queste due funzioni. Infatti, poiché il codice dello script di azione è incluso (include) nel codice del controller, le funzioni trace e dump saranno visibili dagli script.

3.9. Conclusione

Il controller generico ha lo scopo di consentire allo sviluppatore di concentrarsi sulle azioni e sulle viste della propria applicazione. Esso garantisce:

  • la gestione della sessione (ripristino, salvataggio)
  • la verifica della validità delle azioni richieste
  • l'esecuzione dello script associato all'azione
  • l'invio al client della risposta adeguata allo stato risultante dall'esecuzione dell'azione