Skip to content

4. Приклади

4.1. Додаток «Податки»: Вступ

Тут ми представляємо додаток IMPOTS, який надалі буде використовуватися неодноразово. Це додаток, що дозволяє розрахувати податок платника податків. Розглянемо спрощений випадок, коли платник податків має задекларувати лише свою заробітну плату:

  • розраховуємо кількість часток працівника nbParts=nbEnfants/2 +1, якщо він не одружений, nbEnfants/2+2, якщо він одружений, де nbEnfants — кількість його дітей.
  • якщо у нього щонайменше троє дітей, він отримує на півчастини більше
  • розраховується його оподатковуваний дохід R = 0,72 * S, де S — його річна заробітна плата
  • розраховується його сімейний коефіцієнт QF = R / nbParts
  • розраховується його податок I. Розглянемо таку таблицю:
12620,0
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,5
55790
0,35
9316,5
92970
0,4
12106
127860
0,45
16754,5
151250
0,50
23147,5
172040
0,55
30710
195 000
0,60
39312
0
0,65
49062

Кожен рядок має 3 поля. Щоб обчислити податок I, шукаємо перший рядок, у якому QF <= поле1. Наприклад, якщо QF = 23000, то знайдемо рядок

    24740        0.15        2072.5

Тоді податок I дорівнює 0,15*R - 2072,5*nbParts. Якщо QF є таким, що співвідношення QF<=поле1 ніколи не виконується, то використовуються коефіцієнти останнього рядка. У даному випадку:

    0                0.65        49062

що дає податок I = 0,65*R - 49062*nbParts.

Дані, що визначають різні податкові шкали, зберігаються в базі даних ODBC-MySQL. MySQL — це SGBD, що є загальнодоступним і може використовуватися на різних платформах, зокрема Windows та Linux. За допомогою цього SGBD було створено базу даних dbimpots, що містить єдину таблицю з назвою impots. Доступ до бази даних контролюється за допомогою логіна та пароля, в даному випадку — admimpots/mdpimpots. На наступному знімку екрана показано, як використовувати базу даних dbimpots із 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

База даних dbimpots перетворюється на джерело даних ODBC наступним чином:

  • запускається 32-бітний менеджер джерел даних ODBC

Image

  • за допомогою кнопки [Add] додають нове джерело даних ODBC

Image

  • вибираємо драйвер MySQL і виконуємо [Terminer]
  • драйвер MySQL запитує певну інформацію:
1
ім’я DSN, яке слід присвоїти джерелу даних ODBC — може бути будь-яким
2
комп'ютер, на якому виконується SGBD MySQL — у даному випадку localhost. Цікаво зазначити, що база даних може бути віддаленою. Локальні додатки, що використовують джерело даних ODBC, цього навіть не помітять. Це стосується, зокрема, нашого додатка PHP.
3
база даних MySQL, яку слід використовувати. MySQL — це SGBD, що керує реляційними базами даних, які є наборами таблиць, пов’язаних між собою відношеннями. Тут вказується назва бази даних, що керується.
4
ім’я користувача, який має право доступу до цієї бази даних
5
його пароль

4.2. Додаток «Податки»: клас ImpotsDSN

Наш додаток базуватиметься на класі PHP ImpotsDSN, який матиме такі атрибути, конструктор та методи:

<?php
...
     // атрибути
    var $limites;        // таблиця обмежень
    var $coeffR;        // таблиця coeffR
    var $coeffN;        // таблиця coeffN
    var $erreurs;        // таблиця помилок
     // виробник
    function ImpotsDSN($impots){...}
     // метод
    function calculer($personne){...}
  • у постановці задачі ми бачили, що нам потрібно три набори даних
12620,0
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,5
55790
0,35
9316,5
92970
0,4
12106
127860
0,45
16754,5
151250
0,50
23147,5
172040
0,55
30710
195 000
0,60
39312
0
0,65
49062

Перший стовпець буде розміщений в атрибуті $limites класу ImotsDSN, другий — в атрибуті $coeffR, третій — в $coeffN. Ці три атрибути ініціалізуються конструктором класу. Він отримує дані з джерела даних ODBC. Під час створення об’єкта можуть виникнути різні помилки. Ці помилки фіксуються в атрибуті $erreurs у вигляді масиву повідомлень про помилки.

  • Конструктор отримує як параметр словник $impots із такими полями:
// dsn: ім’я DSN джерела даних ODBC, що містить значення таблиць граничних значень, coeffR, coeffN
// user: ім’я користувача, що має право на читання джерела ODBC
// pwd: його пароль
// table: ім’я таблиці, що містить значення (межі, coeffR, coeffN)
// limites: назва стовпця, що містить граничні значення
// coeffR: назва стовпця, що містить значення coeffR       
// coeffN: назва стовпця, що містить значення coeffN

Параметр конструктора надає всю необхідну інформацію для зчитування даних із джерела даних ODBC

  • Після створення об’єкта ImpotsDSN користувачі класу зможуть викликати метод calculerImpots цього об’єкта. Цей метод приймає як параметр словник $personne:
       // $personne["marié"]: так, ні
       // $personne["enfants"]: кількість дітей
       // $personne["salaire"] : річний оклад

Повний клас виглядає так:

<?php
   // визначення класу objImpots
  class ImpotsDSN{

     // атрибути: 3 таблиці даних
    var $limites;        // таблиця обмежень
    var $coeffR;        // масив coeffR
    var $coeffN;        // таблиця coeffN
    var $erreurs;        // таблиця помилок

     // виробник
    function ImpotsDSN($impots){
       // $impots: словник, що містить такі поля
       // dsn: ім’я DSN джерела даних ODBC, що містить значення таблиць граничних значень, coeffR, coeffN
       // user: ім’я користувача, який має право на читання джерела ODBC
       // pwd: його пароль
       // table: ім’я таблиці, що містить значення (межі, coeffR, coeffN)
       // limites: назва стовпця, що містить граничні значення
       // coeffR: назва стовпця, що містить значення coeffR       
       // coeffN: назва стовпця, що містить значення coeffN

       // спочатку помилок немає
      $this->erreurs=array();

             // перевірка виклику
      if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
          ! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
         // помилка
        $this->erreurs[]="Appel incorrect";
         // кінець
        return;
      }//if

       // відкриття бази даних DSN
      $connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
       // помилка?
      if(! $connexion){
         // помилка
        $this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
         // кінець
        return;
      }//if

             // відправлення запиту до бази даних
      $requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
            if(! odbc_execute($requête)){
         // помилка
        $this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
         // кінець
        odbc_close($connexion);
        return;
      }//if

       // обробка результатів запиту
      $this->limites=array();
      $this->coeffR=array();
      $this->coeffN=array();
      while(odbc_fetch_row($requête)){     
          // ще один рядок
        $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

       // закриття бази
      odbc_close($connexion);
    }//конструктор                

    // --------------------------------------------------------------------------
    function calculer($personne){
       // $personne["marié"] : так, ні
       // $personne["enfants"] : кількість дітей
       // $personne["salaire"] : річний оклад

       // чи об’єкт перебуває у належному стані?
      if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;

            // перевірка виклику
      if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
         // помилка
        $this->erreurs[]="Appel incorrect";
         // кінець
        return -1;
      }//if

       // Чи правильні параметри?
      $personne[marié]=strtolower($personne[marié]);
      if($personne[marié]!=oui && $personne[marié]!=non){
           // помилка
        $this->erreurs[]="Statut marital [$personne[marié]] incorrect";
      }//if
      if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
          // помилка
        $this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
      }//if
      if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
          // помилка
        $this->erreurs[]="salaire [$personne[salaire]] incorrect";
      }//if
       // помилки?
      if(count($this->erreurs)!=0) return -1;

      // кількість порцій
      if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
        else $nbParts=$personne[enfants]/2+1;
       // ще 1/2 частки, якщо є щонайменше 3 дітей
      if($personne[enfants]>=3) $nbParts+=0.5;
       // оподатковуваний дохід
      $revenuImposable=0.72*$personne[salaire];
       // сімейний коефіцієнт
      $quotient=$revenuImposable/$nbParts;
       // вказується в кінці таблиці обмежень для зупинки наступного циклу
      $this->limites[$this->nbLimites]=$quotient;
       // розрахунок податку
      $i=0;
      while($quotient>$this->limites[$i]) $i++;
      // оскільки $quotient розміщено в кінці таблиці $limites, попередній цикл
       // не може виходити за межі масиву $limites
       // тепер можна обчислити податок
      return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
    }//calculImpots
  }//клас податків
?>

Програма для тестування може виглядати так:

<?php
     // бібліотеки
  include "ImpotsDSN.php";

     // створення об’єкта «податки»
  $conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

  $objImpots=new ImpotsDSN($conf);

  // помилки?
  if(count($objImpots->erreurs)!=0){
       // повідомлення
    echo "Les erreurs suivantes se sont produites :\n";
    $erreurs=$objImpots->erreurs;
    for($i=0;$i<count($erreurs);$i++){
        echo "$erreurs[$i]\n";
    }//for
    // кінець
    exit(1);
  }//if

   // випробування
  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());

  // кінець
  exit(0);

  // -----------------------------------------------------------
  function calculerImpots($objImpots,$marié,$enfants,$salaire){
       // відлуння
        echo "impots($marié,$enfants,$salaire)\n";    

       // розрахунок податку
    $personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);        
      $montant=$objImpots->calculer($personne);
     // помилки?
    if(count($objImpots->erreurs)!=0){
         // повідомлення
      echo "Les erreurs suivantes se sont produites :\n";
      $erreurs=$objImpots->erreurs;
      for($i=0;$i<count($erreurs);$i++){
          echo "$erreurs[$i]\n";
      }//for
    }else echo "montant=$montant\n";
  }//calculerImp
  • програма тестування «включає» файл, що містить клас ImpotsDSN
  • потім створює об’єкт objImpots класу ImpotsDSN, передаючи конструктору об’єкта необхідну інформацію
  • після створення цього об’єкта його метод calculer буде викликано п’ять разів

Отримано такі результати:


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

Відтепер ми будемо використовувати клас ImpotsDSN, не наводячи його визначення.

4.3. Додаток «Податки»: версія 1

Тепер ми представляємо версію 1 додатка IMPOTS. Розглянемо ситуацію, коли веб-додаток відображає користувачеві інтерфейс HTML для отримання трьох параметрів, необхідних для розрахунку податку:

  • сімейний стан (одружений чи ні)
  • кількість дітей
  • річний дохід

Image

Форма відображається на наступній сторінці PHP:

<?php //податкова декларація ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
           // очищення форми
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//з
      }//видалити      
        </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
         // чи є помилки?
      if(count($requête->erreurs)!=0){
           // перегляд помилок
        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>

Сторінка PHP лише відображає інформацію, яку їй передає головна програма додатка у змінній $requête, яка є об’єктом із такими полями:

chkoui, chknon
атрибути перемикачів «так», «ні» — можуть приймати значення "checked" або "" для ввімкнення або вимкнення відповідного перемикача
enfants
кількість дітей платника податків
salaire
його річний заробіток
impots
сума податку до сплати
erreurs
можливий перелік помилок — може бути порожнім.

Сторінка, що надсилається клієнту, містить скрипт JavaScript із функцією effacer, пов’язаною з кнопкою «Effacer», яка призначена для повернення форми до початкового стану: кнопка не відмічена, поля введення порожні. Слід зауважити, що такого результату не можна було б досягти за допомогою кнопки HTML типу «reset». Адже при використанні цього типу кнопки браузер повертає форму до того стану, в якому він її отримав. Однак у нашому додатку браузер отримує форми, які можуть бути заповненими.

Додаток, що обробляє попередню форму, виглядає так:

<?php
     // обробляє податкову декларацію

     // бібліотеки
  include "ImpotsDSN.php";

  // конфігурація програми
  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);

   // отримання параметрів
  $requête->marié=$_POST["optMarie"];
  $requête->enfants=$_POST["txtEnfants"];
  $requête->salaire=$_POST["txtSalaire"];

   // чи є всі параметри?
  if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
       // підготовка порожньої форми
    $requête->chkoui="";
    $requête->chknon="checked";
    $requête->enfants="";
    $requête->salaire="";
    $requête->impots="";
    $requête->erreurs=array();
     // відображення форми
    include $formulaireImpots;
     // кінець
    exit(0);
  }//if

   // перевірка параметрів
  $requête=vérifier($requête);

   // є помилки?
  if(count($requête->erreurs)!=0){
       // відображення форми
    include "$formulaireImpots";
     // кінець
    exit(0);
  }//if

   // розрахунок відповіді
  $requête=calculerImpots($bdImpots,$requête);

   // помилки?
  if(count($requête->erreurs)!=0){
       // відображення форми помилок
    include "$erreursImpots";
     // кінець
    exit(0);
  }//if

   // відображення форми
  include "$formulaireImpots";
   // кінець
  exit(0);

  // --------------------------------------------------------
  function vérifier($requête){
       // перевірка правильності параметрів запиту

     // на початку помилок немає
    $requête->erreurs=array();

     // статус дійсний?
    $requête->marié=strtolower($requête->marié);
    if($requête->marié!="oui" && $requête->marié!="non"){
         // помилка
      $requête->erreurs[]="Statut marital [$requête->marié] incorrect";
    }

     // чи правильна кількість дочірніх елементів?
    if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
        // помилка
      $requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
    }

     // зарплата дійсна?
    if(! preg_match("/^\s*\d+\s*$/",$requête->salaire)){
        // помилка
      $requête->erreurs[]="Salaire [$requête->salaire] incorrect";
    }

     // стан перемикачів
    if($requête->marié=="oui"){
        $requête->chkoui="checked";
      $requête->chknon="";
    }else{
        $requête->chknon="checked";
      $requête->chkoui="";
    }

     // запит відправляється
    return $requête;
  }//перевірити

  // --------------------------------------------------------
  function calculerImpots($bdImpots,$requête){
       // розраховує суму податку

     // $bdImpots: словник, що містить інформацію, необхідну для зчитування джерела даних ODBC
     // $requête: запит, що містить інформацію про платника податків

     // створюється об’єкт ImpotsDSN 
    $objImpots=new ImpotsDSN($bdImpots);

    // є помилки?
    if(count($objImpots->erreurs)!=0){
         // помилки додаються до запиту
      $requête->erreurs=$objImpots->erreurs;
      // готово
      return $requête;
    }//if

    // розрахунок податку
    $personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
        $requête->impots=$objImpots->calculer($personne);

     // повертаємо результат
    return $requête;
  }//calculerImpots

Коментарі:

  • по-перше, включено клас ImpotsDSN. Саме він лежить в основі розрахунку податків і приховує від нас доступ до бази даних
  • виконується низка ініціалізацій, спрямованих на спрощення обслуговування програми. Якщо параметри зміняться, їхні значення в цьому розділі також будуть змінені. Зазвичай ці конфігураційні значення зберігаються у файлі або базі даних.
  • Ми отримуємо три параметри форми, що відповідають полям HTML, optMarie, txtEnfants, txtSalaire.
  • Ці параметри можуть бути повністю або частково відсутні. Це, зокрема, трапляється під час першого запиту форми. У такому випадку просто надсилається порожня форма.
  • Потім перевіряється правильність трьох отриманих параметрів. Якщо вони виявляються неправильними, форма надсилається назад у тому вигляді, в якому її було введено, але з додатковим переліком помилок.
  • Після перевірки правильності трьох параметрів можна розрахувати податок. Його обчислює функція calculerImpots. Ця функція створює об’єкт типу ImpotsDSN і використовує метод calculer цього об’єкта.
  • Створення об’єкта ImpotsDSN може завершитися невдачею, якщо база даних недоступна. У цьому випадку програма відображає спеціальну сторінку помилки, на якій вказано, які саме помилки сталися.
  • Якщо все пройшло успішно, форма повертається у тому вигляді, в якому її було заповнено, але з доданою сумою податку до сплати.

Сторінка помилок виглядає наступним чином:

<?php    // сторінка помилки ?>
<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
           // відображення помилок
        for($i=0;$i<count($requête->erreurs);$i++){
            echo "<li>".$requête->erreurs[$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Ось кілька прикладів помилок. По-перше, в базу вводиться неправильний пароль:

Image

У формі вказано неправильні дані:

Image

І нарешті, вводяться правильні значення:

Image

4.4. Додаток «Податки»: версія 2

У попередньому прикладі сервер перевіряє правильність параметрів txtEnfants та txtSalaire форми. Тут пропонується перевіряти їх за допомогою скрипта JavaScript, вбудованого в сторінку форми. У цьому випадку перевірку параметрів здійснює браузер. До сервера звертаються лише в тому випадку, якщо параметри є дійсними. Таким чином економиться «пропускна здатність». Сторінка відображення impots-form.php виглядає наступним чином:

<?php //податкова декларація ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
........
      }//очистити

      //------------------------------
      function calculer(){
           // перевірка параметрів перед відправкою на сервер
        with(document.frmImpots){
          //кількість дітей
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // шаблон не перевірено
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
           //зарплата
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // модель не перевірена
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//якщо
          // все гаразд — форму надсилаємо на сервер
          submit();
        }//з
      }//розрахувати            
        </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>

Слід звернути увагу на такі зміни:

  • кнопка Calculer більше не належить до типу submit, а належить до типу button, пов’язаного з функцією під назвою calculer. Саме ця функція здійснюватиме аналіз полів txtEnfants та txTsalaire. Якщо вони правильні, значення з форми будуть надіслані на сервер (submit), інакше з’явиться повідомлення про помилку.

Ось приклад відображення у разі помилки:

Image

4.5. Додаток «Податки»: версія 3

Ми дещо модифікуємо додаток, щоб впровадити в нього поняття сесії. Тепер ми розглядаємо цей додаток як додаток для моделювання розрахунку податків. Таким чином, користувач може моделювати різні «конфігурації» платника податків і бачити, який податок доведеться сплатити за кожною з них. Наведена нижче веб-сторінка демонструє приклад того, що можна отримати:

Image

Програма для відображення наведеної вище сторінки виглядає так:

<?php //податкова декларація ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...
      }//видалити

      //------------------------------
      function calculer(){
...
      }//обчислити            
        </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
         // чи є помилки?
      if(count($requête[erreurs])!=0){
           // перегляд помилок
        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 
         // чи є симуляції?
      else if(count($requête[simulations])!=0){
          // відображення симуляцій
        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>

Програма відображення має незначні відмінності від попередньої версії:

  • словник отримує словник $requête замість об’єкта $requête. Тому записуємо $requête[enfants], а не $requête->діти.
  • Цей словник має поле simulations, яке є двовимірним масивом. $requête[simulations][i] — це симуляція № i. Вона сама по собі є масивом із чотирьох символьних рядків: сімейний стан, кількість дітей, річний оклад, податок до сплати.

Головна програма зазнала більш істотних змін:

<?php
     // обробляє податкову декларацію

     // бібліотеки
  include "ImpotsDSN.php";

   // запуск сеансу
  session_start();

   // налаштування програми
  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);

     // отримання параметрів сесії
  $session=$_SESSION["session"];
   // сесія дійсна?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // запуск нової сесії
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // Чи є помилки?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // відображення сторінки помилок
        include $erreursImpots;
       // кінець
      $session=array();
      terminerSession($session);
    }//if
  }//if

  // отримуємо параметри поточного обміну
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // чи є всі параметри?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // відображення порожньої форми
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // кінець
    terminerSession($session);
  }//if

   // перевірка параметрів
  $requête=vérifier($requête);

   // Чи є помилки?
  if(count($requête[erreurs])!=0){
       // відображення форми
    include "$formulaireImpots";
     // кінець
    terminerSession($session);
  }//if

   // розрахунок податку до сплати
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // ще одне моделювання
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // відображення форми
  include "$formulaireImpots";

   // кінець
  terminerSession($session);

  // --------------------------------------------------------
  function vérifier($requête){
       // перевірка правильності параметрів запиту

     // на початку помилок немає
    $requête[erreurs]=array();

     // статус дійсний?
    $requête[marié]=strtolower($requête[marié]);
    if($requête[marié]!="oui" && $requête[marié]!="non"){
         // помилка
      $requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
    }

     // чи правильна кількість дочірніх елементів?
    if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
        // помилка
      $requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
    }

     // зарплата дійсна?
    if(! preg_match("/^\s*\d+\s*$/",$requête[salaire])){
        // помилка
      $requête[erreurs][]="Salaire [$requête[salaire]] incorrect";
    }

     // стан перемикачів
    if($requête[marié]=="oui"){
        $requête[chkoui]="checked";
      $requête[chknon]="";
    }else{
        $requête[chknon]="checked";
      $requête[chkoui]="";
    }

     // запит відправляється
    return $requête;
  }//перевірити

  // --------------------------------------------------------
  function terminerSession($session){
       // зберігаємо сесію
    $_SESSION[session]=$session;
     // кінець скрипта
    exit(0);
  }//terminerSession
  • По-перше, цей додаток підтримує сесію. Тому на початку скрипта запускається сесія:
  session_start();
  • Сесія зберігає лише одну змінну: словник $session. Цей словник має два поля:
    • objImpots: об’єкт ImpotsDSN. Цей об’єкт зберігається в сесії клієнта, щоб уникнути зайвих послідовних звернень до бази даних ODBC.
    • симуляції: масив симуляцій, що дозволяє з однієї транзакції в іншу зберігати симуляції попередніх транзакцій.
  • Після запуску сеансу отримується змінна $session. Якщо вона ще не існує, це означає, що сеанс запускається. Тоді створюється об’єкт ImpotsDSN та порожній масив симуляцій. За необхідності повідомляються про помилки.
<?php
...
     // отримуємо параметри сесії
  $session=$_SESSION["session"];
   // сесія дійсна?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // запускаємо нову сесію
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // Чи є помилки?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // відображення сторінки помилок
        include $erreursImpots;
       // кінець
      $session=array();
      terminerSession($session);
    }//if
  }//if
  • після успішної ініціалізації сеансу отримуються параметри обміну. Якщо не всі очікувані параметри присутні, надсилається порожня форма.
<?php
...
   // отримуємо параметри поточного обміну
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // чи є всі параметри?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // відображення порожньої форми
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // кінець
    terminerSession($session);
  }//if
  • якщо всі параметри вказано правильно, вони перевіряються. Якщо є помилки, про них повідомляється:
<?php
...
   // перевірка параметрів
  $requête=vérifier($requête);

   // Чи є помилки?
  if(count($requête[erreurs])!=0){
       // відображення форми
    include "$formulaireImpots";
     // кінець
    terminerSession($session);
  }//if
  • якщо параметри правильні, податок розраховується:
<?php
...
   // розрахунок податку до сплати
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));
  • поточне моделювання додається до таблиці моделювань, яка надсилається клієнту разом із формою:
<?php
...
   // ще одне моделювання
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // відображення форми
  include "$formulaireImpots";

   // кінець
  terminerSession($session);
  • у будь-якому випадку скрипт завершується записом змінної $session у сесію. Це відбувається в процедурі terminerSession.
<?php
...

  function terminerSession($session){
       // збереження сеансу
    $_SESSION[session]=$session;
     // кінець скрипта
    exit(0);
  }//terminerSession

На завершення представимо програму відображення помилок доступу до бази даних (impots-erreurs.php):

<?php    // сторінка помилки ?>
<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
           // відображення помилок
        for($i=0;$i<count($requête[erreurs]);$i++){
            echo "<li>".$requête[erreurs][$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Тут також об’єкт $requête було замінено на словник $requête.

4.6. Додаток «Податки»: версія 4

Тепер ми створимо автономний додаток, який буде веб-клієнтом попереднього веб-додатка impots. Цей додаток буде консольним додатком, що запускається з вікна DOS:


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

Параметри веб-клієнта cltImpots такі:

     // синтаксис $0 urlImpots одружений діти зарплата токен
   // клієнт податкової служби, що працює на urlImpots
   // надсилає до цієї служби три відомості: одружений, діти, зарплата
   // можливо, разом із токеном сеансу, якщо його було передано
   // відображає таблицю моделювань сесії

Ось кілька прикладів використання. Спочатку розглянемо перший приклад без сесійного токена.

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php так 2 200000

Jeton de session=[a6297317667bc981c462120987b8dd18]

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

Ми успішно отримали суму податку до сплати (22504 f). Ми також отримали токен сеансу. Тепер ми можемо використати його для другого запиту:

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php так 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]

Ми дійсно отримуємо таблицю симуляцій, надісланих веб-сервером. Отриманий токен, звісно, залишається тим самим. Якщо ми звертаємося до веб-сервісу, коли база даних ще не запущена, отримуємо такий результат:

dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php так 3 200000

Jeton de session=[8369014d5053212bc42f64bbdfb152ee]

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

Під час написання веб-клієнта необхідно точно знати, що саме надсилає сервер у відповідь на різні можливі запити клієнта. Сервер надсилає набір рядків HTML, що містять корисну інформацію та інші дані, які призначені лише для верстки HTML. Регулярні вирази PHP можуть допомогти нам знайти корисну інформацію у потоці рядків, надісланих сервером. Для цього нам потрібно знати точний формат різних відповідей сервера. З цією метою ми можемо звернутися до веб-сервісу за допомогою браузера та переглянути надісланий вихідний код. Зазначимо, що цей метод не дозволяє дізнатися заголовки HTTP у відповіді веб-сервера. Іноді корисно знати саме їх. У такому разі можна скористатися одним із двох універсальних веб-клієнтів, розглянутих у попередньому розділі.

Якщо повторити попередні приклади, ось код HTML, отриманий браузером для таблиці моделювань:

<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>

Це таблиця HTML, єдина в надісланому документі. Отже, регулярний вираз "|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|" повинен дозволяти знаходити в документі різні отримані симуляції. Коли база даних недоступна, отримується такий документ:

    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>

Щоб отримати інформацію про помилку, веб-клієнт повинен знайти у відповіді веб-сервера рядки, що відповідають шаблону: "|<li>(.+?)</li>|".

Тепер ми маємо інструкції щодо того, що потрібно робити, коли користувач запитує розрахунок податку з попереднього консольного клієнта:

  • перевірити, чи всі параметри є дійсними, і, за необхідності, повідомити про помилки.
  • підключитися до URL, вказаного як перший параметр. Для цього слід дотримуватися шаблону загального веб-клієнта, який вже було представлено та розглянуто
  • у потоці відповіді сервера, використовуючи регулярні вирази для:
    • знайти повідомлення про помилки
    • знайти результати моделювання

Код клієнта cltImpots.php виглядає так:

<?php
     // синтаксис $0 urlImpots одружений діти зарплата жетон
   //— клієнт податкової служби, що працює на urlImpots
   // надсилає до цієї служби три відомості: сімейний стан, діти, заробітна плата
   // можливо, разом із токеном сеансу, якщо його було передано
   // відображає таблицю моделювань сесії

   // перевірка кількості аргументів
  if(count($argv)!=5 && count($argv)!=6){
       // помилка
    fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
     // кінець
    exit(1);
  }//if

   // зазначаємо URL
  $urlImpots=analyseURL($argv[1]);
  if(isset($urlImpots[erreur])){
       // помилка
    fwrite(STDERR,"$urlImpots[erreur]\n");
     // кінець
    exit(1);
  }//if

   // аналіз запиту користувача
  $demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
   // помилки?
  if($demande[erreur]){
       // повідомлення
    echo "$demande[erreur]\n";
     // завершено
    exit(1);
  }//if

   // запит можна виконати — використовується токен сеансу
  $impots=getImpots($urlImpots,$demande,$argv[5]);

   // відображаємо сесійний токен
  echo "Jeton de session=[$impots[jeton]]\n";

   // помилки?
  if(count($impots[erreurs])!=0){
       //повідомлення про помилки
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//для
  }else{
       // помилок немає — відображення симуляцій
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

   // завершення програми
  exit(0);

  // --------------------------------------------------------------
  function analyseURL($URL){  
       // аналізує правильність URL $URL
    $url=parse_url($URL);
    // протоколу    
    if(strtolower($url[scheme])!="http"){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // машина
    if(! isset($url[host])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // порт
    if(! isset($url[port])) $url[port]=80;
     // запит
    if(isset($url["query"])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // повернення
    return $url;
  }//analyseURL

  // -----------------------------------------------------------
  function analyseDemande($demande){
       // $demande: рядок для аналізу
     // повинен мати вигляд «одружений, діти, зарплата»
    // правильний формат
    if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
        // неправильний формат
      return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
    }    
     // все гаразд
    $marié=strtolower($champs[1]);
    return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
  }//analyseDemande

  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots: URL для перевірки
     // $demande: словник, що містить поля «дружина», «діти», «зарплата»
     // $jeton: можливий токен сеансу

     // встановлення з'єднання на порту $urlImpots[port] з $urlImpots[host]
    $connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
     // повернення у разі помилки
    if(! $connexion){
      return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
    }//if

         // надсилаються заголовки HTTP на сервер
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));

         // зчитуємо відповідь сервера — спочатку заголовки HTTP
     // перший рядок
    $ligne=fgets($connexion,10000);

    // URL знайдено?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
        // URL недоступний — повернення з помилкою
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//якщо

     // зчитуються інші заголовки HTTP
    while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
       // пошук токена, якщо його ще не знайдено      
      if(! $jeton){
           // пошук рядка set-cookie
        if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
            // токен знайдено — його запам'ятовуємо          
          $jeton=$champs[1];            
        }//if
      }//якщо
    }//наступний рядок

     // читаємо наступний документ
    $document="";
    while($ligne=fread($connexion,10000)){
        $document.=$ligne;
    }//while

     // закрити з'єднання
    fclose($connexion);

     // аналізується документ
    $impots=getInfos($document);

     // повертається результат
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
    }//getImpots

  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: з'єднання з веб-сервером
     // $url: URL, до якого слід звернутися
     // $paramètres: словник параметрів для відправки

     // готуємо POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//while
     // видаляємо останній символ
    $post=substr($post,0,-1);

     // готуємо запит HTTP у форматі 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;

    // надсилаємо запит HTTP
    fwrite($connexion,$HTTP);
    }//POST

  // --------------------------------------------------------------
  function getInfos($document){
       // $document: документ HTML
     // шукається або список помилок
     // або таблицю моделювань

     // підготовка результату
    $impots[erreurs]=array();
    $impots[simulations]=array();

     // чи є помилка?
     // шаблон рядка помилки
    $modErreur="|<li>(.+?)</li>|";
     // пошук у документі
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // отримання помилок
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//для
       // завершено
      return $impots;
    }//якщо

     // зразка одного рядка таблиці моделювання
     // <tr><td>так</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>|";
    // пошук у документі
    if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){    
         // отримання моделювань
      for($i=0;$i<count($champs);$i++){
          $impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
      }//for
       // повернення результату
      return $impots;
    }//if

    // ненормально опинитися тут
    return $impots;
    }//getInfos
?>

Коментарі:

  • програма спочатку перевіряє правильність отриманих параметрів. Для цього вона використовує дві функції: analyseURL, яка перевіряє правильність URL, та analyseDemande, яка перевіряє інші параметри.
  • Розрахунок податку здійснюється за допомогою функції getImpots:
  $impots=getImpots($urlImpots,$demande,$argv[5]);
  • Функція getImpots оголошується наступним чином:
<?php
...
  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots: URL для запиту
     // $demande: словник, що містить поля «дружина», «діти», «зарплата»
     // $jeton: можливий токен сеансу
  • (продовження)
    • $urlImpots — це словник, що містить такі поля:
      • host: комп'ютер, на якому розміщений веб-сервіс
      • port: порт служби
      • path: шлях до запитуваного ресурсу
    • $demande — це словник, що містить такі поля:
      • marié: так/ні — сімейний стан
      • діти: кількість дітей
      • зарплата: річна зарплата
    • $jeton — це сесійний токен.

Функція повертає у результаті словник із такими полями:

  • помилки: масив повідомлень про помилки
  • моделювання: масив моделювань, кожне з яких є масивом із чотирьох елементів (одружений, діти, зарплата, податок)
  • токен: токен сеансу
  • після отримання результату функції getImpots програма виводить результати та завершує роботу:
<?php
...

   // відображається токен сеансу
  echo "Jeton de session=[$impots[jeton]]\n";

   // помилки?
  if(count($impots[erreurs])!=0){
       //повідомлення про помилки
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//для
  }else{
       // помилок немає — відображення симуляцій
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

   // завершення програми
  exit(0);
  • тепер проаналізуємо функцію getImpots($urlImpots,$demande,$jeton), яка повинна
    • створити з’єднання TCP-IP на порту $urlImpots[port] машини $urlImpots[host]
    • надіслати на веб-сервер заголовки HTTP, серед яких, зокрема, має бути токен сеансу, якщо такий є
    • надіслати на веб-сервер запит POST із параметрами, що містяться у словнику $demande
    • проаналізувати відповідь веб-сервера, щоб знайти в ній або список помилок, або таблицю симуляцій.
  • Відправлення заголовків HTTP здійснюється за допомогою функції POST:
<?php
...
         // надсилаємо заголовки HTTP на сервер
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
  • після відправлення заголовків HTTP функція getImpots зчитує всю відповідь сервера та записує її в $document. Відповідь зчитується без аналізу, за винятком першого рядка, який дозволяє нам дізнатися, чи знайшов веб-сервер запитуваний файл URL. Адже якщо файл URL було знайдено, веб-сервер відповідає HTTP/1.X 200 OK, де X залежить від версії HTTP, що використовується.
<?php
...
    // URL знайдено?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
 <?php
...
       // URL недоступний — повернення з помилкою
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//якщо
  • документ аналізується за допомогою функції getInfos:
<?php
...
     // проводиться аналіз документа
    $impots=getInfos($document);
  • Результатом є словник із двома полями:
    • помилки: список помилок — може бути порожнім
    • моделювання: список моделювань — може бути порожнім
  • Отже, функція getImpots може повертати результат у вигляді словника.
<?php
...
     // повертається результат
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
  • тепер проаналізуємо функцію POST:
<?php
...
  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: з'єднання з веб-сервером
     // $url: URL, до якого слід звернутися
     // $paramètres: словник параметрів для відправки

     // готуємо POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//while
     // видаляємо останній символ
    $post=substr($post,0,-1);

     // готуємо запит HTTP у форматі 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;

    // надсилаємо запит HTTP
    fwrite($connexion,$HTTP);
    }//POST

Хоча в рамках програми ми вже мали нагоду запитувати веб-ресурс за допомогою GET, у нас ще не було можливості зробити це за допомогою POST. POST відрізняється від GET способом надсилання параметрів на сервер. Він повинен надсилати такі заголовки HTTP:

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

разом із

  • chemin — шлях до запитуваного веб-ресурсу, у даному випадку $url[path]
  • HTTP/1.X: бажаний протокол HTTP. Тут обрано HTTP/1.0, щоб отримати відповідь одним блоком. HTTP/1.1 дозволяє надсилати відповідь частинами (chunked).
  • N позначає кількість символів, яку клієнт збирається надіслати серверу

N символів, що складають параметри запиту, надсилаються одразу після порожнього рядка, який завершує заголовки HTTP, надіслані на сервер. Ці параметри мають вигляд param1=val1&param2=val2&..., де parami — це ім’я параметра, а vali — його значення. Допустимі значення можуть містити «небажані» символи, такі як пробіли, символ &, символ = тощо. Ці символи слід замінити на рядок %XX, де XX — це їхній шістнадцятковий код. Цю роботу виконує функція PHP urlencode. Зворотну операцію виконує функція urldecode.

Нарешті, слід зазначити, що якщо є токен, він надсилається з заголовком HTTP Cookie: PHPSESSID=токен.

  • Нам залишається розглянути функцію, яка аналізує відповідь сервера:
<?php
...
  // --------------------------------------------------------------
  function getInfos($document){
       // $document: документ HTML
     // шукається або список помилок
     // або таблицю моделювань

     // підготовка результату
    $impots[erreurs]=array();
    $impots[simulations]=array();
  • Нагадаємо, що помилки надсилаються сервером у форматі <li>повідомлення про помилку</li>. Регулярний вираз «|<li>(.+?)</li>|» має дозволити отримати цю інформацію. Тут ми використали знак | для розмежування регулярного виразу замість знака /, оскільки останній також зустрічається в самому регулярному виразі. Функція preg_match_all ($modèle, $document, $champs, PREG_SET_ORDER) дозволяє витягти всі входження $modèle, знайдені в $document. Вони поміщаються в масив $champs. Таким чином, $champs[i] представляє входження № i $modèle у $document. У $champs[$i][0] розміщується рядок, що відповідає шаблону. Якщо в ній були дужки, то рядок, що відповідає першій дужці, розміщується у $champs[$i][1], друга — у $champs[$i][2] і так далі. Отже, код для вилучення помилок буде таким:
<?php
...

     // чи є помилка?
     // шаблон рядка помилки
    $modErreur="|<li>(.+?)</li>|";
     // пошук у документі
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // отримання помилок
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//for
       // завершено
      return $impots;
    }//if

4.7. Додаток «Податки»: Висновок

Ми продемонстрували різні версії нашого клієнт-серверного додатка для розрахунку податків:

  • версія 1: сервіс забезпечується набором програм PHP, клієнтом є браузер. Він виконує лише одне моделювання і не зберігає даних попередніх моделювань.
  • версія 2: додано деякі можливості на стороні браузера шляхом розміщення скриптів JavaScript у документі HTML, який завантажується браузером. Він перевіряє правильність параметрів форми.
  • версія 3: службі надано можливість запам'ятовувати різні симуляції, виконані клієнтом, шляхом управління сесією. Інтерфейс HTML відповідно змінено для відображення цих симуляцій.
  • версія 4: клієнт тепер є автономним консольним додатком. Це дозволяє нам повернутися до написання програмованих веб-клієнтів.

На цьому етапі можна зробити кілька зауважень:

  • версії з 1 по 3 підтримують браузери, які не мають інших можливостей, окрім виконання скриптів JavaScript. Слід зазначити, що користувач завжди має можливість заборонити виконання цих скриптів. У такому разі програма працюватиме лише частково у версії 1 (опція «Очистити» не працюватиме) і взагалі не працюватиме у версіях 2 та 3 (опції «Очистити» та «Обчислити» не працюватимуть). Було б цікаво розробити версію сервісу, яка не використовує скрипти JavaScript.

Під час написання веб-сервісу слід задуматися, на яку аудиторію клієнтів ми орієнтуємося. Якщо ми хочемо охопити якомога більшу кількість клієнтів, то напишемо додаток, який надсилатиме браузерам лише HTML (без JavaScript та аплетів). Якщо ви працюєте в інтранеті й маєте повний контроль над налаштуванням робочих станцій, то можете дозволити собі бути більш вимогливими до клієнта.

Версія 4 — це веб-клієнт, який знаходить необхідну інформацію у потоці HTML, надісланому сервером. Дуже часто ми не маємо контролю над цим потоком. Так буває, коли ми написали клієнт для існуючого в мережі веб-сервісу, яким керує хтось інший. Розглянемо приклад. Припустимо, що наш сервіс моделювання розрахунку податків був розроблений компанією X. Наразі сервіс надсилає результати моделювання у таблиці HTML, і наш клієнт використовує цю особливість для їх отримання. Таким чином, він порівнює кожен рядок відповіді сервера з регулярним виразом:

     // шаблон рядка таблиці моделювання
     // <tr><td>так</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>|";

Припустимо тепер, що розробник додатка змінює візуальний вигляд відповіді, розміщуючи симуляції не в таблиці, а у списку у такому вигляді:

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

У цьому випадку наш веб-клієнт потребуватиме переробки. Саме в цьому полягає постійна загроза для веб-клієнтів додатків, які ми не контролюємо самостійно. XML може вирішити цю проблему:

  • замість генерації HTML служба моделювання генеруватиме XML. У нашому прикладі це може бути
<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>
  • до цієї відповіді може бути прив'язаний стильовий аркуш, який вказує браузерам, як візуально оформити цю відповідь XML
  • програмовані веб-клієнти ігноруватимуть цей стильовий аркуш і отримуватимуть інформацію безпосередньо зі потоку XML відповіді

Якщо розробник сервісу бажає змінити візуальне оформлення результатів, які він надає, він змінить таблицю стилів, а не сам XML. Завдяки таблиці стилів браузери відображатимуть новий вигляд, а програмовані веб-клієнти не потребуватимуть модифікації.