Skip to content

4. Esempi

4.1. Applicazione Imposte: Introduzione

Presentiamo qui l'applicazione IMPOTS, che verrà utilizzata più volte in seguito. Si tratta di un'applicazione che consente di calcolare l'imposta di un contribuente. Consideriamo il caso semplificato di un contribuente che deve dichiarare solo il proprio stipendio:

  • si calcola il numero di quote del dipendente nbParts=nbEnfants/2 +1 se non è sposato, nbEnfants/2+2 se è sposato, dove nbEnfants è il numero dei suoi figli.
  • se ha almeno tre figli, ha diritto a una mezza quota in più
  • si calcola il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
  • si calcola il suo coefficiente familiare QF = R / nbParts
  • si calcola la sua imposta I. Consideriamo la seguente tabella:
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24.740
0,15
2072,5
31810
0,2
3309,5
39.970
0,25
4900
48360
0,3
6898,5
55790
0,35
9316,5
92.970
0,4
12106
127.860
0,45
16754,5
151250
0,50
23147,5
172.040
0,55
30710
195.000
0,60
39312
0
0,65
49062

Ogni riga ha 3 campi. Per calcolare l'imposta I, si cerca la prima riga in cui QF<=campo1. Ad esempio, se QF=23000, si troverà la riga

    24740        0.15        2072.5

L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la relazione QF<=campo1 non è mai soddisfatta, allora vengono utilizzati i coefficienti dell’ultima riga. In questo caso:

    0                0.65        49062

il che dà l'imposta I=0,65*R - 49062*nbParts.

I dati che definiscono le diverse fasce d'imposta sono contenuti in un database ODBC-MySQL. MySQL è un SGBD di dominio pubblico utilizzabile su diverse piattaforme, tra cui Windows e Linux. Con questo SGBD è stato creato un database denominato dbimpots contenente un'unica tabella chiamata impots. L'accesso al database è controllato da un nome utente e una password, in questo caso admimpots/mdpimpots. La schermata seguente mostra come utilizzare il database dbimpots con MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use dbimpots;
Database changed

mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)

mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)

mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)

mysql>quit

Il database dbimpots viene convertito nella fonte dati ODBC nel modo seguente:

  • si avvia il gestore delle fonti dati ODBC a 32 bit

Image

  • si utilizza il pulsante [Add] per aggiungere una nuova fonte dati ODBC

Image

  • si seleziona il driver MySQL e si esegue [Terminer]
  • il driver MySQL richiede alcune informazioni:
1
il nome DSN da assegnare alla fonte di dati ODBC - può essere qualsiasi nome
2
il computer su cui viene eseguito il SGBD MySQL – in questo caso localhost. È interessante notare che il database potrebbe essere un database remoto. Le applicazioni locali che utilizzano la fonte dati ODBC non se ne accorgerebbero. Questo varrebbe in particolare per la nostra applicazione PHP.
3
il database MySQL da utilizzare. MySQL è un SGBD che gestisce database relazionali, ovvero insiemi di tabelle collegate tra loro da relazioni. In questo caso, si indica il nome del database gestito.
4
il nome di un utente che dispone dei diritti di accesso a questo database
5
la sua password

4.2. Applicazione Imposte: la classe ImpotsDSN

La nostra applicazione si baserà sulla classe PHP ImpotsDSN, che avrà i seguenti attributi, costruttore e metodo:

<?php
...
     // attributi
    var $limites;        // tabella dei limiti
    var $coeffR;        // tabella dei coeffR
    var $coeffN;        // tabella dei coeffN
    var $erreurs;        // tabella degli errori
     // costruttore
    function ImpotsDSN($impots){...}
     // metodo
    function calculer($personne){...}
  • Come abbiamo visto nella descrizione del problema, avevamo bisogno di tre serie di dati
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24.740
0,15
2072,5
31810
0,2
3309,5
39.970
0,25
4900
48360
0,3
6898,5
55790
0,35
9316,5
92970
0,4
12106
127.860
0,45
16754,5
151250
0,50
23147,5
172.040
0,55
30710
195.000
0,60
39312
0
0,65
49062

La prima colonna verrà inserita nell'attributo $limites della classe ImotsDSN, la seconda nell'attributo $coeffR, la terza in $coeffN. Questi tre attributi vengono inizializzati dal costruttore della classe. Quest’ultimo recupera i dati da una fonte di dati denominata ODBC. Durante la creazione dell’oggetto possono verificarsi diversi errori. Tali errori vengono riportati nell’attributo $erreurs sotto forma di un array di messaggi di errore.

  • Il costruttore riceve come parametro un dizionario $impots con i seguenti campi:
// dsn: nome DSN della fonte ODBC di dati contenente i valori delle tabelle dei limiti, coeffR, coeffN
// user: nome di un utente con diritti di lettura sulla fonte ODBC
// pwd: la sua password
// tabella: nome della tabella contenente i valori (limiti, coeffR, coeffN)
// limiti: nome della colonna contenente i valori limite
// coeffR: nome della colonna contenente i valori coeffR       
// coeffN: nome della colonna contenente i valori coeffN

Il parametro del costruttore fornisce tutte le informazioni necessarie per leggere i dati dalla fonte dati ODBC

  • una volta creato l’oggetto ImpotsDSN, gli utenti della classe potranno richiamare il metodo calculerImpots dell’oggetto. Quest’ultimo riceve come parametro un dizionario $personne:
       // $persona["marié"]: sì, no
       // $persona["enfants"]: numero di figli
       // $persona["salaire"]: stipendio annuale

La classe completa è la seguente:

<?php
   // definizione di una classe objImpots
  class ImpotsDSN{

     // attributi: le 3 tabelle di dati
    var $limites;        // tabella dei limiti
    var $coeffR;        // tabella dei coeffR
    var $coeffN;        // tabella dei coeffN
    var $erreurs;        // tabella degli errori

     // costruttore
    function ImpotsDSN($impots){
       // $impots: dizionario contenente i seguenti campi
       // dsn: nome DSN della fonte ODBC di dati contenente i valori delle tabelle dei limiti, coeffR, coeffN
       // user: nome di un utente con diritto di lettura sulla fonte ODBC
       // pwd: la sua password
       // tabella: nome della tabella contenente i valori (limiti, coeffR, coeffN)
       // limiti: nome della colonna contenente i valori limite
       // coeffR: nome della colonna contenente i valori coeffR       
       // coeffN: nome della colonna contenente i valori coeffN

       // inizialmente nessun errore
      $this->erreurs=array();

             // verifica della chiamata
      if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
          ! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
         // errore
        $this->erreurs[]="Appel incorrect";
         // fine
        return;
      }//if

       // apertura del database DSN
      $connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
       // errore?
      if(! $connexion){
         // errore
        $this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
         // fine
        return;
      }//if

             // invio di una richiesta al database
      $requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
            if(! odbc_execute($requête)){
         // errore
        $this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
         // fine
        odbc_close($connexion);
        return;
      }//if

       // elaborazione dei risultati della richiesta
      $this->limites=array();
      $this->coeffR=array();
      $this->coeffN=array();
      while(odbc_fetch_row($requête)){     
          // un'altra riga
        $this->limites[]=odbc_result($requête,$impots[limites]);
        $this->coeffR[]=odbc_result($requête,$impots[coeffR]);
        $this->coeffN[]=odbc_result($requête,$impots[coeffN]);
      }//while

       // chiusura base
      odbc_close($connexion);
    }//costruttore                

    // --------------------------------------------------------------------------
    function calculer($personne){
       // $persona["marié"]: sì, no
       // $persona["enfants"] : numero di figli
       // $persona["salaire"] : stipendio annuo

       // L'oggetto è in condizioni corrette?
      if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;

            // verifica della chiamata
      if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
         // errore
        $this->erreurs[]="Appel incorrect";
         // fine
        return -1;
      }//if

       // I parametri sono corretti?
      $personne[marié]=strtolower($personne[marié]);
      if($personne[marié]!=oui && $personne[marié]!=non){
           // errore
        $this->erreurs[]="Statut marital [$personne[marié]] incorrect";
      }//if
      if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
          // errore
        $this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
      }//if
      if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
          // errore
        $this->erreurs[]="salaire [$personne[salaire]] incorrect";
      }//if
       // errori?
      if(count($this->erreurs)!=0) return -1;

      // numero di quote
      if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
        else $nbParts=$personne[enfants]/2+1;
       // mezza quota in più se ci sono almeno 3 bambini
      if($personne[enfants]>=3) $nbParts+=0.5;
       // reddito imponibile
      $revenuImposable=0.72*$personne[salaire];
       // quoziente familiare
      $quotient=$revenuImposable/$nbParts;
       // viene inserito alla fine della tabella dei limiti per interrompere il ciclo successivo
      $this->limites[$this->nbLimites]=$quotient;
       // calcolo dell'imposta
      $i=0;
      while($quotient>$this->limites[$i]) $i++;
      // poiché $quotient è stato inserito alla fine della tabella $limites, il ciclo precedente
       // non può andare oltre i limiti della tabella $limites
       // ora è possibile calcolare l’imposta
      return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
    }//calculImpots
  }//classe imposte
?>

Un programma di test potrebbe essere il seguente:

<?php
     // librerie
  include "ImpotsDSN.php";

     // creazione di un oggetto «imposte»
  $conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

  $objImpots=new ImpotsDSN($conf);

  // errori?
  if(count($objImpots->erreurs)!=0){
       // messaggio
    echo "Les erreurs suivantes se sont produites :\n";
    $erreurs=$objImpots->erreurs;
    for($i=0;$i<count($erreurs);$i++){
        echo "$erreurs[$i]\n";
    }//for
    // fine
    exit(1);
  }//if

   // test
  calculerImpots($objImpots,oui,2,200000);
  calculerImpots($objImpots,non,2,200000);
  calculerImpots($objImpots,oui,3,200000);
  calculerImpots($objImpots,non,3,200000);
  calculerImpots($objImpots,array(),array(),array());

  // fine
  exit(0);

  // -----------------------------------------------------------
  function calculerImpots($objImpots,$marié,$enfants,$salaire){
       // echo
        echo "impots($marié,$enfants,$salaire)\n";    

       // calcolo dell'imposta
    $personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);        
      $montant=$objImpots->calculer($personne);
     // errori?
    if(count($objImpots->erreurs)!=0){
         // messaggio
      echo "Les erreurs suivantes se sont produites :\n";
      $erreurs=$objImpots->erreurs;
      for($i=0;$i<count($erreurs);$i++){
          echo "$erreurs[$i]\n";
      }//per
    }else echo "montant=$montant\n";
  }//calculerImp
  • il programma di test si occupa di "includere" il file contenente la classe ImpotsDSN
  • quindi crea un oggetto objImpots della classe ImpotsDSN passando al costruttore dell'oggetto le informazioni di cui ha bisogno
  • una volta creato tale oggetto, il metodo calculer dello stesso verrà chiamato cinque volte

I risultati ottenuti sono i seguenti:


dos>e:\php43\php.exe test.php
impots(oui,2,200000)
montant=22504
impots(non,2,200000)
montant=33388
impots(oui,3,200000)
montant=16400
impots(non,3,200000)
montant=22504
impots(Array,Array,Array)
Les erreurs suivantes se sont produites :
Statut marital [array] incorrect
Nombre d'enfants [Array] incorrect
salaire [Array] incorrect

D'ora in poi utilizzeremo la classe ImpotsDSN senza ripeterne la definizione.

4.3. Applicazione Imposte: versione 1

Presentiamo ora la versione 1 dell'applicazione IMPOTS. Ci collochiamo nel contesto di un'applicazione web che presenterebbe all'utente un'interfaccia HTML al fine di ottenere i tre parametri necessari al calcolo dell'imposta:

  • lo stato civile (coniugato o non coniugato)
  • il numero di figli
  • lo stipendio annuale

Image

La visualizzazione del modulo avviene tramite la seguente pagina PHP:

<?php //modulo fiscale ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
           // azzeramento del modulo
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//con
      }//cancellare      
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                <input type="radio" name="optMarie" value="oui" <?php echo $requête->chkoui ?>>oui
              <input type="radio" name="optMarie" value="non" <?php echo $requête->chknon ?>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête->enfants ?>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête->salaire ?>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<?php echo $requête->impots ?>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculer"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <?php
         // Ci sono errori?
      if(count($requête->erreurs)!=0){
           // visualizzazione degli errori
        echo "<hr>\n<font color=\"red\">\n";
        echo "Les erreurs suivantes se sont produites<br>";
        echo "<ul>";
        for($i=0;$i<count($requête->erreurs);$i++){
            echo "<li>".$requête->erreurs[$i]."</li>\n";
        }
        echo "</ul>\n</font>\n";
      }//if 
   ?>
 </body>
</html>

La pagina PHP si limita a visualizzare le informazioni che le vengono trasmesse dal programma principale dell'applicazione nella variabile $requête, che è un oggetto con i seguenti campi:

chkoui, chknon
attributi dei pulsanti di opzione sì, no - hanno come possibili valori "checked" o "" per attivare o disattivare il pulsante di opzione corrispondente
enfants
il numero di figli del contribuente
salaire
il suo stipendio annuale
impots
l'importo dell'imposta da pagare
erreurs
un eventuale elenco di errori - può essere vuoto.

La pagina inviata al cliente contiene uno script JavaScript che include una funzione effacer associata al pulsante "Effacer", il cui scopo è riportare il modulo allo stato iniziale: pulsante deselezionato, campi di immissione vuoti. Si noti che questo risultato non poteva essere ottenuto con un pulsante HTML di tipo "reset". Infatti, quando si utilizza questo tipo di pulsante, il browser riporta il modulo allo stato in cui lo ha ricevuto. Tuttavia, nella nostra applicazione, il browser riceve moduli che possono non essere vuoti.

L’applicazione che gestisce il modulo precedente è la seguente:

<?php
     // elabora il modulo fiscale

     // librerie
  include "ImpotsDSN.php";

  // configurazione dell'applicazione
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  $formulaireImpots="impots_form.php";
  $erreursImpots="impots_erreurs.php";
  $bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

   // si recuperano i parametri
  $requête->marié=$_POST["optMarie"];
  $requête->enfants=$_POST["txtEnfants"];
  $requête->salaire=$_POST["txtSalaire"];

   // Sono presenti tutti i parametri?
  if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
       // preparazione del modulo vuoto
    $requête->chkoui="";
    $requête->chknon="checked";
    $requête->enfants="";
    $requête->salaire="";
    $requête->impots="";
    $requête->erreurs=array();
     // visualizzazione del modulo
    include $formulaireImpots;
     // fine
    exit(0);
  }//if

   // verifica dei parametri
  $requête=vérifier($requête);

   // Ci sono errori?
  if(count($requête->erreurs)!=0){
       // visualizzazione modulo
    include "$formulaireImpots";
     // fine
    exit(0);
  }//if

   // calcolo della risposta
  $requête=calculerImpots($bdImpots,$requête);

   // errori?
  if(count($requête->erreurs)!=0){
       // visualizzazione del modulo degli errori
    include "$erreursImpots";
     // fine
    exit(0);
  }//if

   // visualizzazione del modulo
  include "$formulaireImpots";
   // fine
  exit(0);

  // --------------------------------------------------------
  function vérifier($requête){
       // verifica la validità dei parametri della richiesta

     // inizialmente nessun errore
    $requête->erreurs=array();

     // stato valido?
    $requête->marié=strtolower($requête->marié);
    if($requête->marié!="oui" && $requête->marié!="non"){
         // un errore
      $requête->erreurs[]="Statut marital [$requête->marié] incorrect";
    }

     // numero di figli valido?
    if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
        // un errore
      $requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
    }

     // stipendio valido?
    if(! preg_match("/^\s*\d+\s*$/",$requête->salaire)){
        // errore
      $requête->erreurs[]="Salaire [$requête->salaire] incorrect";
    }

     // stato dei pulsanti di opzione
    if($requête->marié=="oui"){
        $requête->chkoui="checked";
      $requête->chknon="";
    }else{
        $requête->chknon="checked";
      $requête->chkoui="";
    }

     // si invia la richiesta
    return $requête;
  }//verifica

  // --------------------------------------------------------
  function calculerImpots($bdImpots,$requête){
       // calcolo dell'importo dell'imposta

     // $bdImpots: dizionario contenente le informazioni necessarie per leggere la fonte di dati ODBC
     // $requête: la richiesta contenente le informazioni sul contribuente

     // si crea un oggetto ImpotsDSN 
    $objImpots=new ImpotsDSN($bdImpots);

    // Ci sono errori?
    if(count($objImpots->erreurs)!=0){
         // si inseriscono gli errori nella richiesta
      $requête->erreurs=$objImpots->erreurs;
      // finito
      return $requête;
    }//if

    // calcolo dell'imposta
    $personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
        $requête->impots=$objImpots->calculer($personne);

     // si restituisce il risultato
    return $requête;
  }//calculerImpots

Commenti:

  • innanzitutto, viene inclusa la classe ImpotsDSN. È questa la classe alla base del calcolo delle imposte e che ci nasconderà l'accesso al database
  • vengono effettuate alcune inizializzazioni volte a facilitare la manutenzione dell’applicazione. Se i parametri cambiano, si modificheranno i loro valori in questa sezione. In generale, questi valori di configurazione vengono piuttosto memorizzati in un file o in un database.
  • Si recuperano i tre parametri del modulo corrispondenti ai campi HTML, optMarie, txtEnfants, txtSalaire.
  • Questi parametri possono essere totalmente o parzialmente assenti. Ciò si verifica in particolare al momento della richiesta iniziale del modulo. In tal caso, ci si limita a inviare un modulo vuoto.
  • Successivamente viene verificata la validità dei tre parametri recuperati. Se risultano errati, il modulo viene rinviato così come è stato inserito, ma con l’aggiunta di un elenco di errori.
  • Una volta verificata la validità dei tre parametri, è possibile procedere al calcolo dell’imposta. Quest’ultima viene calcolata dalla funzione calculerImpots. Questa funzione crea un oggetto di tipo ImpotsDSN e utilizza il metodo calculer di tale oggetto.
  • La creazione dell’oggetto ImpotsDSN potrebbe non andare a buon fine se il database non è disponibile. In tal caso, l’applicazione visualizza una pagina di errore specifica che indica gli errori verificatisi.
  • Se tutto è andato a buon fine, il modulo viene restituito così come è stato compilato, ma con l’aggiunta dell’importo dell’imposta da pagare.

La pagina di errore è la seguente:

<?php    // pagina di errore ?>
<html>
    <head>
      <title>Application impôts indisponible</title>
  </head>
  <body background="/poly/impots/images/standard.jpg">
      <h3>Calcul d'impôts</h3>
    <hr>
    Application indisponible. Recommencez ultérieurement.<br><br>
    <font color="red">
           Les erreurs suivantes se sont produites<br>
        <ul>
      <?php
           // visualizzazione degli errori
        for($i=0;$i<count($requête->erreurs);$i++){
            echo "<li>".$requête->erreurs[$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Ecco alcuni esempi di errori. Innanzitutto, si inserisce una password errata:

Image

Si inseriscono dati errati nel modulo:

Image

Infine, si inseriscono valori corretti:

Image

4.4. Applicazione Imposte: versione 2

Nell'esempio precedente, la validità dei parametri txtEnfants e txtSalaire del modulo viene verificata dal server. Qui ci proponiamo di verificarla tramite uno script JavaScript incluso nella pagina del modulo. In questo modo è il browser a verificare i parametri. Il server viene quindi interpellato solo se questi sono validi. In questo modo si risparmia «larghezza di banda». La pagina di visualizzazione impots-form.php diventa la seguente:

<?php //modulo fiscale ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
........
      }//cancella

      //------------------------------
      function calculer(){
           // verifica dei parametri prima dell'invio al server
        with(document.frmImpots){
          //numero di figli
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // il modello non è stato verificato
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
           //stipendio
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // il modello non è stato verificato
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//se
          // Va bene - inviamo il modulo al server
          submit();
        }//con
      }//calcolare            
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
............
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
........
 </body>
</html>

Si notino le seguenti modifiche:

  • il pulsante Calculer non è più di tipo submit, bensì di tipo button, associato a una funzione denominata calculer. È questa funzione che effettuerà l'analisi dei campi txtEnfants e txTsalaire. Se i valori sono corretti, i dati del modulo verranno inviati al server (submit); in caso contrario, verrà visualizzato un messaggio di errore.

Ecco un esempio di visualizzazione in caso di errore:

Image

4.5. Applicazione Imposte: versione 3

Modifichiamo leggermente l’applicazione per introdurvi il concetto di sessione. Consideriamo ora che l’applicazione sia un programma di simulazione del calcolo delle imposte. Un utente può quindi simulare diverse «configurazioni» di contribuente e vedere quale sarebbe, per ciascuna di esse, l’imposta da pagare. La pagina web qui sotto fornisce un esempio di ciò che si potrebbe ottenere:

Image

Il codice per la visualizzazione della pagina sopra riportata è il seguente:

<?php //modulo fiscale ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...
      }//cancella

      //------------------------------
      function calculer(){
...
      }//calcolare            
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                <input type="radio" name="optMarie" value="oui" <?php echo $requête[chkoui] ?>>oui
              <input type="radio" name="optMarie" value="non" <?php echo $requête[chknon] ?>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête[enfants] ?>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête[salaire] ?>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<?php echo $requête[impots] ?>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <?php
         // ci sono errori?
      if(count($requête[erreurs])!=0){
           // visualizzazione degli errori
        echo "<hr>\n<font color=\"red\">\n";
        echo "Les erreurs suivantes se sont produites<br>";
        echo "<ul>";
        for($i=0;$i<count($requête[erreurs]);$i++){
            echo "<li>".$requête[erreurs][$i]."</li>\n";
        }
        echo "</ul>\n</font>\n";
      }//if 
         // ci sono simulazioni?
      else if(count($requête[simulations])!=0){
          // visualizzazione delle simulazioni
        echo "<hr>\n<h2>Résultats des simulations</h2>\n";
        echo "<table border=\"1\">\n";
        echo "<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>\n";
        for($i=0;$i<count($requête[simulations]);$i++){
            echo "<tr>".
              "<td>".$requête[simulations][$i][0]."</td>".
              "<td>".$requête[simulations][$i][1]."</td>".
              "<td>".$requête[simulations][$i][2]."</td>".
              "<td>".$requête[simulations][$i][3]."</td>".
            "</tr>\n";                                    
        }//for
        echo "</table>\n";
      }//if 
   ?>
 </body>
</html>

Il programma di visualizzazione presenta alcune lievi differenze rispetto alla versione precedente:

  • il dizionario riceve un dizionario $requête invece di un oggetto $requête. Si scrive quindi $requête[enfants] e non $requête->figli.
  • Questo dizionario ha un campo simulations che è un array bidimensionale. $requête[simulations][i] è la simulazione n. i. Questa è a sua volta una matrice di quattro stringhe di caratteri: stato civile, numero di figli, stipendio annuale, imposta da pagare.

Il programma principale, invece, ha subito modifiche più sostanziali:

<?php
     // elabora il modulo fiscale

     // librerie
  include "ImpotsDSN.php";

   // avvio della sessione
  session_start();

   // configurazione dell'applicazione
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  $formulaireImpots="impots_form.php";
  $erreursImpots="impots_erreurs.php";
  $bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

     // recupera i parametri della sessione
  $session=$_SESSION["session"];
   // sessione valida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // si avvia una nuova sessione
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // Ci sono errori?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // visualizzazione della pagina degli errori
        include $erreursImpots;
       // fine
      $session=array();
      terminerSession($session);
    }//if
  }//if

  // si recuperano i parametri dello scambio in corso
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // Sono presenti tutti i parametri?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // visualizzazione del modulo vuoto
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fine
    terminerSession($session);
  }//if

   // verifica dei parametri
  $requête=vérifier($requête);

   // Ci sono errori?
  if(count($requête[erreurs])!=0){
       // visualizzazione del modulo
    include "$formulaireImpots";
     // fine
    terminerSession($session);
  }//if

   // calcolo dell'imposta da pagare
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // un'altra simulazione
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // visualizzazione del modulo
  include "$formulaireImpots";

   // fine
  terminerSession($session);

  // --------------------------------------------------------
  function vérifier($requête){
       // verifica la validità dei parametri della richiesta

     // Inizialmente nessun errore
    $requête[erreurs]=array();

     // stato valido?
    $requête[marié]=strtolower($requête[marié]);
    if($requête[marié]!="oui" && $requête[marié]!="non"){
         // un errore
      $requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
    }

     // numero di figli valido?
    if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
        // un errore
      $requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
    }

     // stipendio valido?
    if(! preg_match("/^\s*\d+\s*$/",$requête[salaire])){
        // errore
      $requête[erreurs][]="Salaire [$requête[salaire]] incorrect";
    }

     // stato dei pulsanti di opzione
    if($requête[marié]=="oui"){
        $requête[chkoui]="checked";
      $requête[chknon]="";
    }else{
        $requête[chknon]="checked";
      $requête[chkoui]="";
    }

     // si invia la richiesta
    return $requête;
  }//verificare

  // --------------------------------------------------------
  function terminerSession($session){
       // si memorizza la sessione
    $_SESSION[session]=$session;
     // fine script
    exit(0);
  }//terminerSession
  • Innanzitutto, questa applicazione gestisce una sessione. Si avvia quindi una sessione all'inizio dello script:
  session_start();
  • La sessione memorizza una sola variabile: il dizionario $session. Questo dizionario ha due campi:
    • objImpots: un oggetto ImpotsDSN. Questo oggetto viene memorizzato nella sessione del cliente per evitare accessi successivi non necessari al database ODBC.
    • simulazioni: la tabella delle simulazioni per conservare, da uno scambio all’altro, le simulazioni degli scambi precedenti.
  • Una volta avviata la sessione, si recupera la variabile $session. Se questa non esiste ancora, significa che la sessione è in fase di avvio. Si creano quindi un oggetto ImpotsDSN e un array di simulazioni vuoto. Se necessario, vengono segnalati degli errori.
<?php
...
     // si recuperano i parametri della sessione
  $session=$_SESSION["session"];
   // sessione valida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // si avvia una nuova sessione
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // Ci sono errori?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // visualizzazione della pagina degli errori
        include $erreursImpots;
       // fine
      $session=array();
      terminerSession($session);
    }//if
  }//if
  • una volta che la sessione è stata correttamente inizializzata, vengono recuperati i parametri dello scambio. Se non sono presenti tutti i parametri previsti, viene inviato un modulo vuoto.
<?php
...
   // si recuperano i parametri dello scambio in corso
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // Sono presenti tutti i parametri?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // visualizzazione del modulo vuoto
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fine
    terminerSession($session);
  }//if
  • Se tutti i parametri sono corretti, vengono verificati. Se ci sono errori, vengono segnalati:
<?php
...
   // verifica dei parametri
  $requête=vérifier($requête);

   // Ci sono errori?
  if(count($requête[erreurs])!=0){
       // visualizzazione del modulo
    include "$formulaireImpots";
     // fine
    terminerSession($session);
  }//if
  • se i parametri sono corretti, l'imposta viene calcolata:
<?php
...
   // calcolo dell'imposta da pagare
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));
  • la simulazione in corso viene aggiunta alla tabella delle simulazioni, che viene inviata al cliente insieme al modulo:
<?php
...
   // un'altra simulazione
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // visualizzazione del modulo
  include "$formulaireImpots";

   // fine
  terminerSession($session);
  • In ogni caso, lo script termina salvando la variabile $session nella sessione. Ciò avviene nella procedura terminerSession.
<?php
...

  function terminerSession($session){
       // memorizzazione della sessione
    $_SESSION[session]=$session;
     // fine script
    exit(0);
  }//terminerSession

Concludiamo presentando il programma di visualizzazione degli errori di accesso al database (impots-erreurs.php):

<?php    // pagina di errore ?>
<html>
    <head>
      <title>Application impôts indisponible</title>
  </head>
  <body background="/poly/impots/images/standard.jpg">
      <h3>Calcul d'impôts</h3>
    <hr>
    Application indisponible. Recommencez ultérieurement.<br><br>
    <font color="red">
           Les erreurs suivantes se sont produites<br>
        <ul>
      <?php
           // visualizzazione degli errori
        for($i=0;$i<count($requête[erreurs]);$i++){
            echo "<li>".$requête[erreurs][$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Anche in questo caso, l'oggetto $requête è stato sostituito da un dizionario $requête.

4.6. Applicazione Imposte: versione 4

Ora creeremo un'applicazione autonoma che fungerà da client web dell'applicazione web impots precedente. L'applicazione sarà un'applicazione da console avviata da una finestra DOS:


dos>e:\php43\php.exe cltImpots.php
Syntaxe cltImpots.php urlImpots marié enfants salaire [jeton]

I parametri del client web cltImpots sono i seguenti:

     // sintassi $0 urlImpots sposato figli stipendio gettone
   // cliente di un servizio fiscale che opera su urlImpots
   // invia a questo servizio le tre informazioni: stato civile, figli, stipendio
   // eventualmente con il token di sessione, se è stato trasmesso
   // visualizza la tabella delle simulazioni della sessione

Ecco alcuni esempi di utilizzo. Innanzitutto, un primo esempio senza token di sessione.

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php sì 2 200000

Jeton de session=[a6297317667bc981c462120987b8dd18]

Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]

Abbiamo recuperato correttamente l'importo dell'imposta da pagare (22504 f). Abbiamo recuperato anche il token di sessione. Ora possiamo utilizzarlo per una seconda interrogazione:

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php sì 3 200000 a6297317667bc981c462120987b8dd18

Jeton de session=[a6297317667bc981c462120987b8dd18]

Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
[oui,3,200000,16400]

Otteniamo effettivamente la tabella delle simulazioni inviate dal server web. Il token recuperato rimane ovviamente lo stesso. Se interroghiamo il servizio web mentre il database non è stato avviato, otteniamo il seguente risultato:

dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php sì 3 2.000.000

Jeton de session=[8369014d5053212bc42f64bbdfb152ee]

Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)

Quando si sviluppa un client web, è necessario sapere esattamente cosa invia il server in risposta alle diverse richieste possibili da parte di un client. Il server invia una serie di righe HTML contenenti informazioni utili e altre che servono solo per l'impaginazione HTML. Le espressioni regolari di PHP possono aiutarci a individuare le informazioni utili nel flusso delle righe inviate dal server. A tal fine, dobbiamo conoscere il formato esatto delle diverse risposte del server. Per farlo, possiamo interrogare il servizio web con un browser e esaminare il codice sorgente che è stato inviato. Si noti che questo metodo non consente di conoscere le intestazioni della risposta del server web. A volte è utile conoscerle. In tal caso, è possibile utilizzare uno dei due client web generici visti nel capitolo precedente.

Se ripetiamo gli esempi precedenti, ecco il codice HTML ricevuto dal browser per la tabella delle simulazioni:

<h2>Résultats des simulations</h2>
<table border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
<tr><td>oui</td><td>3</td><td>200000</td><td>16400</td></tr>
</table>

Si tratta di una tabella HTML, l'unica presente nel documento inviato. Pertanto, l'espressione regolare "|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|" dovrebbe consentire di individuare nel documento le diverse simulazioni ricevute. Quando il database non è disponibile, si riceve il seguente documento:

    Application indisponible. Recommencez ultérieurement.<br><br>
    <font color="red">
           Les erreurs suivantes se sont produites<br>
        <ul>
      <li>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</li>
        </ul>
    </font>

Per recuperare l'errore, il client web dovrà cercare nella risposta del server web le righe corrispondenti al modello: "|<li>(.+?)</li>|".

Ora disponiamo delle linee guida su cosa fare quando l’utente richiede il calcolo dell’imposta dal precedente client console:

  • verificare che tutti i parametri siano validi ed eventualmente segnalare gli errori.
  • connettersi a URL fornito come primo parametro. A tal fine si seguirà il modello del client web generico già presentato e analizzato
  • nel flusso della risposta del server, utilizzare espressioni regolari per:
    • individuare i messaggi di errore
    • individuare i risultati delle simulazioni

Il codice del cliente cltImpots.php è il seguente:

<?php
     // sintassi $0 urlImpots coniugato figli stipendio gettone
   // cliente di un servizio fiscale che opera su urlImpots
   // invia a questo servizio le tre informazioni: stato civile, figli, stipendio
   // eventualmente con il token di sessione, se è stato trasmesso
   // visualizza la tabella delle simulazioni della sessione

   // verifica del numero di argomenti
  if(count($argv)!=5 && count($argv)!=6){
       // errore
    fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
     // fine
    exit(1);
  }//if

   // si nota il URL
  $urlImpots=analyseURL($argv[1]);
  if(isset($urlImpots[erreur])){
       // errore
    fwrite(STDERR,"$urlImpots[erreur]\n");
     // fine
    exit(1);
  }//if

   // analisi della richiesta dell'utente
  $demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
   // errori?
  if($demande[erreur]){
       // messaggio
    echo "$demande[erreur]\n";
     // è finito
    exit(1);
  }//if

   // è possibile effettuare la richiesta - si utilizza il token di sessione
  $impots=getImpots($urlImpots,$demande,$argv[5]);

   // si visualizza il token di sessione
  echo "Jeton de session=[$impots[jeton]]\n";

   // errori?
  if(count($impots[erreurs])!=0){
       //messaggi di errore
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//per
  }else{
       // nessun errore - visualizzazione delle simulazioni
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

   // fine del programma
  exit(0);

  // --------------------------------------------------------------
  function analyseURL($URL){  
       // analizza la validità di URL $URL
    $url=parse_url($URL);
    // il protocollo    
    if(strtolower($url[scheme])!="http"){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//se
     // la macchina
    if(! isset($url[host])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // la porta
    if(! isset($url[port])) $url[port]=80;
     // la richiesta
    if(isset($url["query"])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // risposta
    return $url;
  }//analyseURL

  // -----------------------------------------------------------
  function analyseDemande($demande){
       // $demande: stringa da analizzare
     // deve avere la forma: coniugato, figli, stipendio
    // formato valido
    if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
        // formato non valido
      return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
    }    
     // va bene
    $marié=strtolower($champs[1]);
    return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
  }//analyseDemande

  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots: URL da verificare
     // $demande: dizionario contenente i campi «moglie», «figli» e «stipendio»
     // $jeton: un eventuale token di sessione

     // apertura di una connessione sulla porta $urlImpots[port] di $urlImpots[host]
    $connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
     // ritorno in caso di errore
    if(! $connexion){
      return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
    }//if

         // si inviano le intestazioni HTTP al server
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));

         // si legge la risposta del server - prima le intestazioni HTTP
     // la prima riga
    $ligne=fgets($connexion,10000);

    // URL trovato?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
        // URL non accessibile - ritorno con errore
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//se

     // si leggono le altre intestazioni HTTP
    while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
       // ricerca del token se non è stato ancora trovato      
      if(! $jeton){
           // ricerca della riga set-cookie
        if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
            // il token è stato trovato - lo si memorizza          
          $jeton=$champs[1];            
        }//if
      }//if
    }//riga successiva

     // si legge il documento successivo
    $document="";
    while($ligne=fread($connexion,10000)){
        $document.=$ligne;
    }//while

     // si chiude la connessione
    fclose($connexion);

     // si analizza il documento
    $impots=getInfos($document);

     // si restituisce il risultato
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
    }//getImpots

  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: connessione al server web
     // $url: l'URL da interrogare
     // $paramètres: dizionario dei parametri da inviare

     // si prepara il POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//mentre
     // si rimuove l'ultimo carattere
    $post=substr($post,0,-1);

     // si prepara la richiesta HTTP nel formato HTTP/1.0
    $HTTP="POST $url[path] HTTP/1.0\n";
    $HTTP.="Content-type: application/x-www-form-urlencoded\n";
    $HTTP.="Content-length: ".strlen($post)."\n";
    $HTTP.="Connection: close\n";
    if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
    $HTTP.="\n";
    $HTTP.=$post;

    // si invia la richiesta HTTP
    fwrite($connexion,$HTTP);
    }//POST

  // --------------------------------------------------------------
  function getInfos($document){
       // $document: documento HTML
     // si cerca o l’elenco degli errori
     // oppure la tabella delle simulazioni

     // preparazione del risultato
    $impots[erreurs]=array();
    $impots[simulations]=array();

     // c'è un errore?
     // modello della riga di errore
    $modErreur="|<li>(.+?)</li>|";
     // ricerca nel documento
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // recupero degli errori
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//per
       // completato
      return $impots;
    }//se

     // modello di una riga della tabella delle simulazioni
     // <tr><td>sì</td><td>2</td><td>200000</td><td>22504</td></tr>
    $modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
    // ricerca nel documento
    if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){    
         // recupero delle simulazioni
      for($i=0;$i<count($champs);$i++){
          $impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
      }//per
       // restituzione del risultato
      return $impots;
    }//if

    // non è normale arrivare a questo punto
    return $impots;
    }//getInfos
?>

Commenti:

  • il programma inizia verificando la validità dei parametri ricevuti. A tal fine utilizza due funzioni: analyseURL, che verifica la validità di URL, e analyseDemande, che verifica gli altri parametri.
  • Il calcolo dell’imposta viene effettuato dalla funzione getImpots:
  $impots=getImpots($urlImpots,$demande,$argv[5]);
  • La funzione getImpots è dichiarata come segue:
<?php
...
  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots: URL da interrogare
     // $demande: dizionario contenente i campi «moglie», «figli», «stipendio»
     // $jeton: un eventuale token di sessione
  • (continua)
    • $urlImpots è un dizionario contenente i seguenti campi:
      • host: macchina su cui risiede il servizio web
      • port: la sua porta di servizio
      • path: il percorso della risorsa richiesta
    • $demande è un dizionario contenente i seguenti campi:
      • sposato: sì/no: stato civile
      • figli: numero di figli
      • stipendio: stipendio annuale
    • $jeton è il token di sessione.

La funzione restituisce come risultato un dizionario con i seguenti campi:

  • errori: array di messaggi di errore
  • simulazioni: array di simulazioni, dove ogni simulazione è a sua volta un array di quattro elementi (stato civile, figli, stipendio, imposta)
  • token: il token di sessione
  • una volta ottenuto il risultato di getImpots, il programma visualizza i risultati e termina:
<?php
...

   // viene visualizzato il token di sessione
  echo "Jeton de session=[$impots[jeton]]\n";

   // errori?
  if(count($impots[erreurs])!=0){
       //messaggi di errore
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//per
  }else{
       // nessun errore - visualizzazione delle simulazioni
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

   // fine del programma
  exit(0);
  • Analizziamo ora la funzione getImpots($urlImpots,$demande,$jeton) che deve
    • creare una connessione TCP-IP sulla porta $urlImpots[port] del computer $urlImpots[host]
    • inviare al server web le intestazioni HTTP, in particolare il token di sessione, se presente
    • inviare al server web una richiesta POST con i parametri contenuti nel dizionario $demande
    • analizzare la risposta del server web per individuare al suo interno un elenco di errori o una tabella di simulazioni.
  • L'invio delle intestazioni HTTP avviene tramite la funzione POST:
<?php
...
         // invio delle intestazioni HTTP al server
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
  • una volta inviate le intestazioni HTTP, la funzione getImpots legge l'intera risposta del server e la inserisce in $document. La risposta viene letta senza essere analizzata, tranne che per la prima riga, che ci permette di sapere se il server web ha trovato o meno l'URL che gli è stato richiesto. Infatti, se l'URL è stato trovato, il server web risponde HTTP/1.X 200 OK, dove X dipende dalla versione di HTTP utilizzata.
<?php
...
    // URL trovato?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
 <?php
...
       // URL non accessibile - ritorno con errore
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//se
  • il documento viene analizzato utilizzando la funzione getInfos:
<?php
...
     // si analizza il documento
    $impots=getInfos($document);
  • Il risultato restituito è un dizionario a due campi:
    • errori: elenco degli errori - può essere vuoto
    • simulazioni: elenco delle simulazioni - può essere vuoto
  • Una volta fatto ciò, la funzione getImpots può restituire il proprio risultato sotto forma di dizionario.
<?php
...
     // si restituisce il risultato
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
  • Analizziamo ora la funzione POST:
<?php
...
  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: connessione al server web
     // $url: l'URL da interrogare
     // $paramètres: dizionario dei parametri da inviare

     // si prepara il POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//mentre
     // si rimuove l'ultimo carattere
    $post=substr($post,0,-1);

     // si prepara la richiesta HTTP nel formato HTTP/1.0
    $HTTP="POST $url[path] HTTP/1.0\n";
    $HTTP.="Content-type: application/x-www-form-urlencoded\n";
    $HTTP.="Content-length: ".strlen($post)."\n";
    $HTTP.="Connection: close\n";
    if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
    $HTTP.="\n";
    $HTTP.=$post;

    // si invia la richiesta HTTP
    fwrite($connexion,$HTTP);
    }//POST

Sebbene nel programma avessimo già avuto modo di richiedere una risorsa web tramite un GET, non avevamo ancora avuto occasione di farlo con un POST. Il codice POST differisce dal codice GET per quanto riguarda il modo in cui invia i parametri al server. Deve inviare le seguenti intestazioni HTTP:

POST chemin HTTP/1.X
Content-type: application/x-www-form-urlencoded
Content-length: N

con

  • chemin: percorso della risorsa web richiesta, in questo caso $url[path]
  • HTTP/1.X: il protocollo HTTP desiderato. In questo caso è stato scelto HTTP/1.0 per ricevere la risposta in un unico blocco. HTTP/1.1 consente l'invio della risposta in più blocchi (chunked).
  • N indica il numero di caratteri che il client sta per inviare al server

I N caratteri che costituiscono i parametri della richiesta vengono inviati immediatamente dopo la riga vuota che conclude le intestazioni HTTP inviate al server. Questi parametri hanno il formato param1=val1&param2=val2&..., dove parami è il nome del parametro e vali il suo valore. I valori vali possono contenere caratteri "fastidiosi" come spazi, il carattere &, il carattere =, ecc. Questi caratteri devono essere sostituiti da una stringa %XX, dove XX è il loro codice esadecimale. La funzione PHP urlencode svolge questa operazione. L’operazione inversa viene eseguita da urldecode.

Infine, si noti che se si dispone di un token, questo viene inviato con un'intestazione HTTP Cookie: PHPSESSID=token.

  • Resta da esaminare la funzione che analizza la risposta del server:
<?php
...
  // --------------------------------------------------------------
  function getInfos($document){
       // $document: documento HTML
     // si cerca o l’elenco degli errori
     // oppure la tabella delle simulazioni

     // preparazione del risultato
    $impots[erreurs]=array();
    $impots[simulations]=array();
  • Ricordiamo che gli errori vengono inviati dal server nella forma <li>messaggio di errore</li>. L'espressione regolare "|<li>(.+?)</li>|" deve consentire di recuperare queste informazioni. In questo caso abbiamo utilizzato il segno | per delimitare l'espressione regolare anziché il segno /, poiché quest'ultimo è presente anche all'interno dell'espressione stessa. La funzione preg_match_all ($modèle, $document, $champs, PREG_SET_ORDER) consente di recuperare tutte le occorrenze di $modèle presenti in $document. Queste vengono inserite nell'array $champs. Pertanto, $champs[i] rappresenta l'occorrenza n. i di $modèle in $document. In $champs[$i][0] viene inserita la stringa corrispondente al modello. Se quest’ultimo contenesse delle parentesi, la stringa corrispondente alla prima parentesi viene inserita in $champs[$i][1], la seconda in $champs[$i][2] e così via. Il codice per recuperare gli errori sarà quindi il seguente:
<?php
...

     // c'è un errore?
     // modello della riga di errore
    $modErreur="|<li>(.+?)</li>|";
     // ricerca nel documento
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // recupero degli errori
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//per
       // fine
      return $impots;
    }//if

4.7. Applicazione fiscale: Conclusione

Abbiamo illustrato diverse versioni della nostra applicazione client-server per il calcolo delle imposte:

  • versione 1: il servizio è fornito da un insieme di programmi PHP, mentre il client è un browser. Esegue una sola simulazione e non conserva la memoria di quelle precedenti.
  • versione 2: si aggiungono alcune funzionalità lato browser inserendo script JavaScript nel documento HTML caricato dal browser stesso. Il browser verifica la validità dei parametri del modulo.
  • versione 3: si consente al servizio di ricordare le diverse simulazioni effettuate da un client gestendo una sessione. L’interfaccia HTML viene modificata di conseguenza per visualizzarle.
  • versione 4: il client è ora un'applicazione console autonoma. Ciò ci consente di tornare alla scrittura di client web programmati.

A questo punto, è possibile fare alcune osservazioni:

  • le versioni da 1 a 3 supportano browser che non dispongono di altre funzionalità se non quella di eseguire script JavaScript. Si noti che un utente ha sempre la possibilità di disabilitare l’esecuzione di questi script. L’applicazione funzionerà quindi solo parzialmente nella versione 1 (l’opzione «Cancella» non funzionerà) e per nulla nelle versioni 2 e 3 (le opzioni «Cancella» e «Calcola» non funzioneranno). Potrebbe essere interessante prevedere una versione del servizio che non utilizzi script JavaScript.

Quando si sviluppa un servizio Web, è necessario chiedersi a quali tipi di client ci si rivolge. Se si vuole raggiungere il maggior numero possibile di client, si scriverà un’applicazione che invii ai browser solo HTML (senza JavaScript né applet). Se si lavora all’interno di una intranet e si ha il controllo sulla configurazione delle postazioni, è possibile permettersi di essere più esigenti a livello di client.

La versione 4 è un client web che recupera le informazioni di cui ha bisogno all’interno del flusso HTML inviato dal server. Molto spesso non si ha il controllo su questo flusso. È il caso in cui si sia scritto un client per un servizio web esistente sulla rete e gestito da qualcun altro. Facciamo un esempio. Supponiamo che il nostro servizio di simulazioni per il calcolo delle imposte sia stato sviluppato da una società X. Attualmente il servizio invia le simulazioni in una tabella HTML e il nostro client sfrutta questa caratteristica per recuperarle. Confronta quindi ogni riga della risposta del server con l’espressione regolare:

     // modello di una riga della tabella delle simulazioni
     // <tr><td>sì</td><td>2</td><td>200000</td><td>22504</td></tr>
    $modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";

Supponiamo ora che lo sviluppatore dell'applicazione modifichi l'aspetto visivo della risposta inserendo le simulazioni non in una tabella ma in un elenco nella forma:

Résultat des simulations
<ul>
    <li>oui,2,200000,22504
  <li>non,2,200000,33388
</ul>  

In questo caso, il nostro client web dovrà essere riscritto. È questa la minaccia costante che grava sui client web delle applicazioni di cui non si ha il controllo diretto. XML può fornire una soluzione a questo problema:

  • invece di generare HTML, il servizio di simulazione genererà XML. Nel nostro esempio, potrebbe essere
<simulations>
    <entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
  <simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
  • a questa risposta potrebbe essere associato un foglio di stile che indichi ai browser l'aspetto visivo da attribuire a questa risposta XML
  • i client web programmati ignorerebbero questo foglio di stile e recupererebbero le informazioni direttamente dal flusso XML della risposta

Se il progettista del servizio desidera modificare la presentazione visiva dei risultati forniti, modificherà il foglio di stile e non il XML. Grazie al foglio di stile, i browser visualizzeranno la nuova forma visiva e i client web programmati non dovranno essere modificati.