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
195000
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<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. وهنا:

    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 بالطريقة التالية:

  • يتم تشغيل مدير مصادر البيانات ODBC 32 بت

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
...
     // attributs
    var $limites;        // tableau des limites
    var $coeffR;        // tableau des coeffR
    var $coeffN;        // tableau des coeffN
    var $erreurs;        // tableau des erreurs
     // constructeur
    function ImpotsDSN($impots){...}
     // méthode
    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
195000
0.60
39312
0
0.65
49062

سيتم وضع العمود الأول في السمة $limites للفئة ImotsDSN، والثاني في السمة $coeffR، والثالث في $coeffN. يتم تهيئة هذه السمات الثلاث بواسطة منشئ الفئة. ويقوم المنشئ باسترداد البيانات من مصدر البيانات ODBC. قد تحدث أخطاء متنوعة أثناء إنشاء الكائن. ويتم الإبلاغ عن هذه الأخطاء في السمة $erreurs في شكل مصفوفة من رسائل الأخطاء.

  • يتلقى المنشئ كمعلمة قاموسًا $impots يحتوي على الحقول التالية:
// dsn : nom DSN de la source ODBC de données contenant les valeurs des tableaux limites, coeffR, coeffN
// user : nom d'un utilisateur ayant un droit de lecture sur la source ODBC
// pwd : son mot de passe
// table : nom de la table contenant les valeurs (limites, coeffR, coeffN)
// limites : nom de la colonne contenant les valeurs limites
// coeffR : nom de la colonne contenant les valeurs coeffR       
// coeffN : nom de la colonne contenant les valeurs coeffN

توفر معلمة المنشئ جميع المعلومات اللازمة لقراءة البيانات من مصدر البيانات ODBC

  • بمجرد إنشاء الكائن ImpotsDSN، سيتمكن مستخدمو الفئة من استدعاء الطريقة calculerImpots الخاصة بالكائن. وتتلقى هذه الطريقة كمعلمة قاموسًا $personne:
       // $personne["marié"] : oui, non
       // $personne["enfants"] : nombre d'enfants
       // $personne["salaire"] : salaire annuel

الفئة الكاملة هي كما يلي:

<?php
   // définition d'une classe objImpots
  class ImpotsDSN{

     // attributs : les 3 tableaux de données
    var $limites;        // tableau des limites
    var $coeffR;        // tableau des coeffR
    var $coeffN;        // tableau des coeffN
    var $erreurs;        // tableau des erreurs

     // constructeur
    function ImpotsDSN($impots){
       // $impots : dictionnaire contenant les champs suivants
       // dsn : nom DSN de la source ODBC de données contenant les valeurs des tableaux limites, coeffR, coeffN
       // user : nom d'un utilistaeur ayant un droit de lecture sur la source ODBC
       // pwd : son mot de passe
       // table : nom de la table contenant les valeurs (limites, coeffR, coeffN)
       // limites : nom de la colonne contenant les valeurs limites
       // coeffR : nom de la colonne contenant les valeurs coeffR       
       // coeffN : nom de la colonne contenant les valeurs coeffN

       // au départ pas d'erreurs
      $this->erreurs=array();

             // vérification de l'appel
      if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
          ! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
         // erreur
        $this->erreurs[]="Appel incorrect";
         // fin
        return;
      }//if

       // ouverture de la base DSN
      $connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
       // erreur ?
      if(! $connexion){
         // erreur
        $this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
         // fin
        return;
      }//if

             // émission d'une requête sur la base
      $requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
            if(! odbc_execute($requête)){
         // erreur
        $this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
         // fin
        odbc_close($connexion);
        return;
      }//if

       // exploitation des résultats de la requête
      $this->limites=array();
      $this->coeffR=array();
      $this->coeffN=array();
      while(odbc_fetch_row($requête)){     
          // une ligne de plus
        $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

       // fermeture base
      odbc_close($connexion);
    }//constructeur                

    // --------------------------------------------------------------------------
    function calculer($personne){
       // $personne["marié"] : oui, non
       // $personne["enfants"] : nombre d'enfants
       // $personne["salaire"] : salaire annuel

       // l'objet est-il dans un état correct ?
      if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;

            // vérification de l'appel
      if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
         // erreur
        $this->erreurs[]="Appel incorrect";
         // fin
        return -1;
      }//if

       // les paramètres sont-ils corrects ?
      $personne[marié]=strtolower($personne[marié]);
      if($personne[marié]!=oui && $personne[marié]!=non){
           // erreur
        $this->erreurs[]="Statut marital [$personne[marié]] incorrect";
      }//if
      if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
          // erreur
        $this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
      }//if
      if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
          // erreur
        $this->erreurs[]="salaire [$personne[salaire]] incorrect";
      }//if
       // des erreurs ?
      if(count($this->erreurs)!=0) return -1;

      // nombre de parts
      if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
        else $nbParts=$personne[enfants]/2+1;
       // une 1/2 part de plus si au moins 3 enfants
      if($personne[enfants]>=3) $nbParts+=0.5;
       // revenu imposable
      $revenuImposable=0.72*$personne[salaire];
       // quotient familial
      $quotient=$revenuImposable/$nbParts;
       // est mis à la fin du tableau limites pour arrêter la boucle qui suit
      $this->limites[$this->nbLimites]=$quotient;
       // calcul de l'impôt
      $i=0;
      while($quotient>$this->limites[$i]) $i++;
      // du fait qu'on a placé $quotient à la fin du tableau $limites, la boucle précédente
       // ne peut déborder du tableau $limites
       // maintenant on peut calculer l'impôt
      return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
    }//calculImpots
  }//classe impôts
?>

قد يكون برنامج الاختبار كما يلي:

<?php
     // bibilothèques
  include "ImpotsDSN.php";

     // création d'un objet impots
  $conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

  $objImpots=new ImpotsDSN($conf);

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

   // des essais
  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());

  // fin
  exit(0);

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

       // calcul de l'impôt
    $personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);        
      $montant=$objImpots->calculer($personne);
     // des erreurs ?
    if(count($objImpots->erreurs)!=0){
         // msg
      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 //formulaire des impôts ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
           // raz du formulaire
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//with
      }//effacer      
        </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
         // y-a-t-il des erreurs ?
      if(count($requête->erreurs)!=0){
           // affichage des erreurs
        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
قائمة بالأخطاء (إن وجدت) - قد تكون فارغة.

تحتوي الصفحة المرسلة إلى العميل على برنامج جافا سكريبت يتضمن دالة effacer مرتبطة بالزر "Effacer" الذي يعمل على إعادة النموذج إلى حالته الأولية: الزر غير محدد، وحقول الإدخال فارغة. تجدر الإشارة إلى أنه لم يكن من الممكن الحصول على هذه النتيجة باستخدام زر HTML من النوع «reset». ففي الواقع، عند استخدام هذا النوع من الأزرار، يعيد المتصفح النموذج إلى الحالة التي استلمه بها. لكن في تطبيقنا، يتلقى المتصفح نماذج قد تكون غير فارغة.

التطبيق الذي يعالج النموذج السابق هو التالي:

<?php
     // traite le formulaire des impôts

     // bibilothèques
  include "ImpotsDSN.php";

  // configuration de l'application
  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);

   // on récupère les paramètres
  $requête->marié=$_POST["optMarie"];
  $requête->enfants=$_POST["txtEnfants"];
  $requête->salaire=$_POST["txtSalaire"];

   // a-t-on tous les paramètres ?
  if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
       // préparation formulaire vide
    $requête->chkoui="";
    $requête->chknon="checked";
    $requête->enfants="";
    $requête->salaire="";
    $requête->impots="";
    $requête->erreurs=array();
     // affichage formulaire
    include $formulaireImpots;
     // fin
    exit(0);
  }//if

   // vérification paramètres
  $requête=vérifier($requête);

   // des erreurs ?
  if(count($requête->erreurs)!=0){
       // affichage formulaire
    include "$formulaireImpots";
     // fin
    exit(0);
  }//if

   // calcul de la réponse
  $requête=calculerImpots($bdImpots,$requête);

   // des erreurs ?
  if(count($requête->erreurs)!=0){
       // affichage formulaire erreurs
    include "$erreursImpots";
     // fin
    exit(0);
  }//if

   // affichage formulaire
  include "$formulaireImpots";
   // fin
  exit(0);

  // --------------------------------------------------------
  function vérifier($requête){
       // vérifie la validité des paramètres de la requête

     // au départ pas d'erreurs
    $requête->erreurs=array();

     // statut valide ?
    $requête->marié=strtolower($requête->marié);
    if($requête->marié!="oui" && $requête->marié!="non"){
         // une erreur
      $requête->erreurs[]="Statut marital [$requête->marié] incorrect";
    }

     // nbre d'enfants valide ?
    if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
        // une erreur
      $requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
    }

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

     // état des boutons radio
    if($requête->marié=="oui"){
        $requête->chkoui="checked";
      $requête->chknon="";
    }else{
        $requête->chknon="checked";
      $requête->chkoui="";
    }

     // on rend la requête
    return $requête;
  }//vérifier

  // --------------------------------------------------------
  function calculerImpots($bdImpots,$requête){
       // calcule le montant de l'impôt

     // $bdImpots : dictionnaire contenant les informations nécessaires pour lire la source de données ODBC
     // $requête : la requête contenant les informations sur le contribuable

     // on construit un objet ImpotsDSN 
    $objImpots=new ImpotsDSN($bdImpots);

    // des erreurs ?
    if(count($objImpots->erreurs)!=0){
         // on met les erreurs dans la requête
      $requête->erreurs=$objImpots->erreurs;
      // fini
      return $requête;
    }//if

    // calcul de l'impôt
    $personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
        $requête->impots=$objImpots->calculer($personne);

     // on rend le résultat
    return $requête;
  }//calculerImpots

تعليقات:

  • أولاً، تم تضمين الفئة ImpotsDSN. وهي التي تشكل أساس حساب الضرائب وستحجب عنا الوصول إلى قاعدة البيانات
  • يتم إجراء عدد من عمليات التهيئة بهدف تسهيل صيانة التطبيق. في حالة تغيير المعلمات، سيتم تعديل قيمها في هذا القسم. وبشكل عام، يتم تخزين قيم التكوين هذه في ملف أو قاعدة بيانات.
  • يتم استرداد المعلمات الثلاثة من النموذج المطابقة للحقول HTML و optMarie و txtEnfants و txtSalaire.
  • قد تكون هذه المعلمات غائبة كليًا أو جزئيًا. ويحدث ذلك على وجه الخصوص عند الطلب الأولي للنموذج. في هذه الحالة، نكتفي بإرسال نموذج فارغ.
  • بعد ذلك، يتم التحقق من صحة المعلمات الثلاثة المستلمة. إذا تبين أنها غير صحيحة، يتم إعادة إرسال النموذج كما تم إدخاله مع إضافة قائمة بالأخطاء.
  • بمجرد التحقق من صحة المعلمات الثلاثة، يمكن إجراء حساب الضريبة. ويتم حساب الضريبة بواسطة الدالة calculerImpots. تقوم هذه الدالة بإنشاء كائن من النوع ImpotsDSN وتستخدم الطريقة calculer الخاصة بهذا الكائن.
  • قد يفشل إنشاء الكائن ImpotsDSN إذا كانت قاعدة البيانات غير متاحة. في هذه الحالة، يعرض التطبيق صفحة خطأ محددة تشير إلى الأخطاء التي حدثت.
  • إذا سارت الأمور على ما يرام، يتم إرجاع النموذج كما تم إدخاله مع إضافة مبلغ الضريبة المستحقة.

صفحة الأخطاء هي كما يلي:

<?php    // page d'erreur ?>
<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
           // affichage des erreurs
        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 الخاصة بالنموذج. ونقترح هنا التحقق منها بواسطة برنامج نصي جافا سكريبت مضمن في صفحة النموذج. وبذلك يتولى المتصفح مهمة التحقق من صحة المعلمات. ولا يتم اللجوء إلى الخادم إلا إذا كانت هذه المعلمات صالحة. وبذلك يتم توفير «عرض النطاق الترددي». وتصبح صفحة العرض impots-form.php كما يلي:

<?php //formulaire des impôts ?>

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

      //------------------------------
      function calculer(){
           // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
           //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer            
        </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. إذا كانت صحيحة، فسيتم إرسال قيم النموذج إلى الخادم (إرسال)، وإلا فسيتم عرض رسالة خطأ.

فيما يلي مثال على الشاشة التي تظهر في حالة حدوث خطأ:

Image

4.5. تطبيق الضرائب: الإصدار 3

نقوم بإجراء تعديل طفيف على التطبيق لإدخال مفهوم «الجلسة». نعتبر الآن أن التطبيق هو تطبيق لمحاكاة حساب الضرائب. وبذلك يمكن للمستخدم محاكاة «تكوينات» مختلفة للمكلف بالضريبة ومعرفة مقدار الضريبة المستحقة الدفع لكل منها. تقدم صفحة الويب أدناه مثالاً على ما يمكن الحصول عليه:

Image

برنامج عرض الصفحة أعلاه هو التالي:

<?php //formulaire des impôts ?>

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

      //------------------------------
      function calculer(){
...
      }//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
         // y-a-t-il des erreurs ?
      if(count($requête[erreurs])!=0){
           // affichage des erreurs
        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 
         // y-a-t-il des simulations ?
      else if(count($requête[simulations])!=0){
          // affichage des simulations
        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->children.
  • يحتوي هذا القاموس على حقل simulations وهو مصفوفة ثنائية الأبعاد. $requête[simulations][i] هي المحاكاة رقم i. وهذه المحاكاة هي بدورها مصفوفة مكونة من أربع سلاسل أحرف: الحالة الاجتماعية، وعدد الأبناء، والراتب السنوي، والضريبة المستحقة.

أما البرنامج الرئيسي فقد تطور بشكل أعمق:

<?php
     // traite le formulaire des impôts

     // bibilothèques
  include "ImpotsDSN.php";

   // démarrage session
  session_start();

   // configuration de l'application
  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);

     // on récupère les paramètres de la session
  $session=$_SESSION["session"];
   // session valide ?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // on démarre une nouvelle session
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // des erreurs ?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // affichage page d'erreurs
        include $erreursImpots;
       // fin
      $session=array();
      terminerSession($session);
    }//if
  }//if

  // on récupère les paramètres de l'échange en cours
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // a-t-on tous les paramètres ?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // affichage formulaire vide
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fin
    terminerSession($session);
  }//if

   // vérification paramètres
  $requête=vérifier($requête);

   // des erreurs ?
  if(count($requête[erreurs])!=0){
       // affichage formulaire
    include "$formulaireImpots";
     // fin
    terminerSession($session);
  }//if

   // calcul de l'impôt à payer
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // une simulation de plus
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // affichage formulaire
  include "$formulaireImpots";

   // fin
  terminerSession($session);

  // --------------------------------------------------------
  function vérifier($requête){
       // vérifie la validité des paramètres de la requête

     // au départ pas d'erreurs
    $requête[erreurs]=array();

     // statut valide ?
    $requête[marié]=strtolower($requête[marié]);
    if($requête[marié]!="oui" && $requête[marié]!="non"){
         // une erreur
      $requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
    }

     // nbre d'enfants valide ?
    if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
        // une erreur
      $requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
    }

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

     // état des boutons radio
    if($requête[marié]=="oui"){
        $requête[chkoui]="checked";
      $requête[chknon]="";
    }else{
        $requête[chknon]="checked";
      $requête[chkoui]="";
    }

     // on rend la requête
    return $requête;
  }//vérifier

  // --------------------------------------------------------
  function terminerSession($session){
       // on mémorise la session
    $_SESSION[session]=$session;
     // fin script
    exit(0);
  }//terminerSession
  • أولاً، يدير هذا التطبيق جلسة عمل. لذا، يتم بدء جلسة عمل في بداية البرنامج النصي:
  session_start();
  • تخزن الجلسة متغيرًا واحدًا فقط: القاموس $session. يحتوي هذا القاموس على حقلين:
    • objImpots: كائن ImpotsDSN. يتم تخزين هذا الكائن في جلسة عمل العميل لتجنب عمليات الوصول المتتالية غير الضرورية إلى قاعدة البيانات ODBC.
    • المحاكاة: جدول المحاكاة لاستدعاء محاكاة المعاملات السابقة من معاملة إلى أخرى.
  • بمجرد بدء الجلسة، يتم استرداد المتغير $session. إذا لم يكن هذا المتغير موجودًا بعد، فهذا يعني أن الجلسة قد بدأت. عندئذٍ يتم إنشاء كائن ImpotsDSN وجدول محاكاة فارغ. إذا لزم الأمر، يتم الإبلاغ عن الأخطاء.
<?php
...
     // on récupère les paramètres de la session
  $session=$_SESSION["session"];
   // session valide ?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // on démarre une nouvelle session
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // des erreurs ?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // affichage page d'erreurs
        include $erreursImpots;
       // fin
      $session=array();
      terminerSession($session);
    }//if
  }//if
  • بمجرد تهيئة الجلسة بشكل صحيح، يتم استرداد معلمات التبادل. إذا لم تكن جميع المعلمات المتوقعة موجودة، يتم إرسال نموذج فارغ.
<?php
...
   // on récupère les paramètres de l'échange en cours
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // a-t-on tous les paramètres ?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // affichage formulaire vide
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fin
    terminerSession($session);
  }//if
  • إذا كانت جميع المعلمات موجودة، يتم التحقق منها. وفي حالة وجود أخطاء، يتم الإبلاغ عنها:
<?php
...
   // vérification paramètres
  $requête=vérifier($requête);

   // des erreurs ?
  if(count($requête[erreurs])!=0){
       // affichage formulaire
    include "$formulaireImpots";
     // fin
    terminerSession($session);
  }//if
  • إذا كانت المعلمات صحيحة، يتم حساب الضريبة:
<?php
...
   // calcul de l'impôt à payer
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));
  • تُضاف المحاكاة الجارية إلى جدول المحاكاة، ثم يُرسل هذا الجدول إلى العميل مع النموذج:
<?php
...
   // une simulation de plus
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // affichage formulaire
  include "$formulaireImpots";

   // fin
  terminerSession($session);
  • في جميع الأحوال، ينتهي البرنامج النصي بتسجيل المتغير $session في الجلسة. ويتم ذلك في الإجراء terminerSession.
<?php
...

  function terminerSession($session){
       // on mémorise la session
    $_SESSION[session]=$session;
     // fin script
    exit(0);
  }//terminerSession

ونختتم بعرض برنامج عرض أخطاء الوصول إلى قاعدة البيانات (impots-erreurs.php):

<?php    // page d'erreur ?>
<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
           // affichage des erreurs
        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 هي كما يلي:

     // syntaxe $0 urlImpots marié enfants salaire jeton
   // client d'un service d'impôts fonctionnant sur urlImpots
   // envoie à ce service les trois informations : marié, enfants, salaire
   // éventuellement avec le jeton de session si celui-ci a été passé
   // affiche le tableau des simulations de la session

فيما يلي بعض الأمثلة على الاستخدام. أولاً، مثال أول بدون رمز جلسة.

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php oui 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 oui 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 oui 3 200000

Jeton de session=[8369014d5053212bc42f64bbdfb152ee]

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

عند كتابة عميل ويب مبرمج، من الضروري معرفة ما يرسله الخادم بالضبط استجابةً للطلبات المختلفة المحتملة من العميل. يرسل الخادم مجموعة من الأسطر HTML تتضمن معلومات مفيدة وأخرى موجودة فقط من أجل التنسيق HTML. يمكن أن تساعدنا التعبيرات النمطية في العثور على المعلومات المفيدة ضمن تدفق الأسطر التي يرسلها الخادم. وللقيام بذلك، نحتاج إلى معرفة التنسيق الدقيق للردود المختلفة الصادرة عن الخادم. ولهذا الغرض، يمكننا استعلام خدمة الويب باستخدام متصفح والاطلاع على شفرة المصدر التي تم إرسالها. تجدر الإشارة إلى أن هذه الطريقة لا تسمح بمعرفة رؤوس الاستجابة 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
     // syntaxe $0 urlImpots marié enfants salaire jeton
   // client d'un service d'impôts fonctionnant sur urlImpots
   // envoie à ce service les trois informations : marié, enfants, salaire
   // éventuellement avec le jeton de session si celui-ci a été passé
   // affiche le tableau des simulations de la session

   // vérification nbre d'arguments
  if(count($argv)!=5 && count($argv)!=6){
       // erreur
    fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
     // fin
    exit(1);
  }//if

   // on note l'URL
  $urlImpots=analyseURL($argv[1]);
  if(isset($urlImpots[erreur])){
       // erreur
    fwrite(STDERR,"$urlImpots[erreur]\n");
     // fin
    exit(1);
  }//if

   // analyse de la demande de l'utilisateur
  $demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
   // erreurs ?
  if($demande[erreur]){
       // msg
    echo "$demande[erreur]\n";
     // c'est fini
    exit(1);
  }//if

   // on peut faire la demande - on utilise le jeton de session
  $impots=getImpots($urlImpots,$demande,$argv[5]);

   // on affiche le jeton de session
  echo "Jeton de session=[$impots[jeton]]\n";

   // des erreurs ?
  if(count($impots[erreurs])!=0){
       //msg d'erreurs
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//for
  }else{
       // pas d'erreurs - affichage des simulations
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

   // fin du programme
  exit(0);

  // --------------------------------------------------------------
  function analyseURL($URL){  
       // analyse la validité de l'URL $URL
    $url=parse_url($URL);
    // le protocole    
    if(strtolower($url[scheme])!="http"){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // la machine
    if(! isset($url[host])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // le port
    if(! isset($url[port])) $url[port]=80;
     // la requête
    if(isset($url["query"])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // retour
    return $url;
  }//analyseURL

  // -----------------------------------------------------------
  function analyseDemande($demande){
       // $demande : chaîne à analyser
     // doit être de la forme marié, enfants, salaire
    // format valide
    if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
        // format invalide
      return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
    }    
     // c'est bon
    $marié=strtolower($champs[1]);
    return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
  }//analyseDemande

  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots : URL à interroger
     // $demande : dictionnaire contenant les champs marie, enfants, salaire
     // $jeton : un éventuel jeton de session

     // ouverture d'une connexion sur le port $urlImpots[port] de $urlImpots[host]
    $connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
     // retour si erreur
    if(! $connexion){
      return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
    }//if

         // on envoie les entetes HTTP au serveur
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));

         // on lit la réponse du serveur - d'abord les entêtes HTTP
     // la première ligne
    $ligne=fgets($connexion,10000);

    // URL trouvée ?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
        // URL inaccessible - retour avec erreur
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//if

     // on lit les autres entêtes HTTP
    while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
       // recherche du jeton si on ne l'a pas encore trouvé      
      if(! $jeton){
           // recherche de la ligne set-cookie
        if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
            // on a trouvé le jeton - on le mémorise          
          $jeton=$champs[1];            
        }//if
      }//if
    }//ligne suivante

     // on lit le document qui suit
    $document="";
    while($ligne=fread($connexion,10000)){
        $document.=$ligne;
    }//while

     // on ferme la connexion
    fclose($connexion);

     // on analyse le document
    $impots=getInfos($document);

     // on rend le résultat
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
    }//getImpots

  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion : la connexion a serveur web
     // $url : l'URL à interroger
     // $paramètres : dictionnaire des paramètres à poster

     // on prépare le POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//while
     // on enlève le dernier caractère
    $post=substr($post,0,-1);

     // on prépare la requête HTTP au format 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;

    // on envoie la requête HTTP
    fwrite($connexion,$HTTP);
    }//POST

  // --------------------------------------------------------------
  function getInfos($document){
       // $document : document HTML
     // on cherche soit la liste d'erreurs
     // soit le tableau des simulations

     // préparation du résultat
    $impots[erreurs]=array();
    $impots[simulations]=array();

     // y-a-t-il une erreur ?
     // modèle de la ligne d'erreur
    $modErreur="|<li>(.+?)</li>|";
     // recherche dans le document
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // récupération des erreurs
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//for
       // fini
      return $impots;
    }//if

     // modèle d'une ligne du tableau des simulations
     // <tr><td>oui</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>|";
    // recherche dans le document
    if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){    
         // récupération des simulations
      for($i=0;$i<count($champs);$i++){
          $impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
      }//for
       // retour résultat
      return $impots;
    }//if

    // pas normal d'arriver là
    return $impots;
    }//getInfos
?>

تعليقات:

  • يبدأ البرنامج بالتحقق من صحة المعلمات التي تلقّاها. ويستخدم لهذا الغرض دالتين: analyseURL التي تتحقق من صحة URL، وanalyseDemande التي تتحقق من صحة المعلمات الأخرى.
  • يتم حساب الضريبة بواسطة الدالة getImpots:
  $impots=getImpots($urlImpots,$demande,$argv[5]);
  • يتم تعريف الدالة getImpots على النحو التالي:
<?php
...
  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots : URL à interroger
     // $demande : dictionnaire contenant les champs marie, enfants, salaire
     // $jeton : un éventuel jeton de session
  • (تابع)
    • $urlImpots هو قاموس يحتوي على الحقول التالية:
      • host: الجهاز الذي توجد عليه خدمة الويب
      • port: منفذ الخدمة
      • path: مسار المورد المطلوب
    • $demande هو قاموس يحتوي على الحقول التالية:
      • marié: نعم/لا: الحالة الاجتماعية
      • الأطفال: عدد الأطفال
      • الراتب: الراتب السنوي
    • $jeton هو رمز الجلسة.

تُرجع الدالة كنتيجة قاموسًا يحتوي على الحقول التالية:

  • الأخطاء: مصفوفة من رسائل الأخطاء
  • المحاكاة: مصفوفة من عمليات المحاكاة، حيث تكون كل عملية محاكاة عبارة عن مصفوفة مكونة من أربعة عناصر (متزوج، أطفال، راتب، ضريبة)
  • الرمز: رمز الجلسة
  • بمجرد الحصول على نتيجة getImpots، يعرض البرنامج النتائج وينتهي:
<?php
...

   // on affiche le jeton de session
  echo "Jeton de session=[$impots[jeton]]\n";

   // des erreurs ?
  if(count($impots[erreurs])!=0){
       //msg d'erreurs
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//for
  }else{
       // pas d'erreurs - affichage des simulations
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

   // fin du programme
  exit(0);
  • لنحلل الآن الدالة getImpots($urlImpots,$demande,$jeton) التي يجب أن
    • إنشاء اتصال TCP-IP على المنفذ $urlImpots[port] الخاص بالجهاز $urlImpots[host]
    • إرسال الرؤوس HTTP إلى خادم الويب، والتي يتوقعها، ولا سيما رمز الجلسة إن وجد
    • إرسال طلب POST إلى خادم الويب مع المعلمات الواردة في القاموس $demande
    • تحليل استجابة خادم الويب للعثور إما على قائمة بالأخطاء أو جدول بالمحاكاة.
  • يتم إرسال الرؤوس HTTP باستخدام الدالة POST:
<?php
...
         // on envoie les entetes HTTP au serveur
    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 trouvée ?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
 <?php
...
       // URL inaccessible - retour avec erreur
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//if
  • يتم تحليل المستند باستخدام الدالة getInfos:
<?php
...
     // on analyse le document
    $impots=getInfos($document);
  • والنتيجة هي قاموس ذو حقلين:
    • الأخطاء: قائمة بالأخطاء - قد تكون فارغة
    • المحاكاة: قائمة المحاكاة - قد تكون فارغة
  • وبذلك، يمكن للدالة getImpots إرجاع نتيجتها في شكل قاموس.
<?php
...
     // on rend le résultat
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
  • لنحلل الآن الدالة POST:
<?php
...
  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion : la connexion a serveur web
     // $url : l'URL à interroger
     // $paramètres : dictionnaire des paramètres à poster

     // on prépare le POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//while
     // on enlève le dernier caractère
    $post=substr($post,0,-1);

     // on prépare la requête HTTP au format 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;

    // on envoie la requête 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 : document HTML
     // on cherche soit la liste d'erreurs
     // soit le tableau des simulations

     // préparation du résultat
    $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
...

     // y-a-t-il une erreur ?
     // modèle de la ligne d'erreur
    $modErreur="|<li>(.+?)</li>|";
     // recherche dans le document
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // récupération des erreurs
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//for
       // fini
      return $impots;
    }//if

4.7. تطبيق الضرائب: الخلاصة

لقد عرضنا إصدارات مختلفة من تطبيقنا العميل-الخادم لحساب الضرائب:

  • الإصدار 1: يتم توفير الخدمة من خلال مجموعة من البرامج PHP، أما العميل فهو متصفح. ويقوم بإجراء محاكاة واحدة فقط ولا يحتفظ بسجل للمحاكاة السابقة.
  • الإصدار 2: تمت إضافة بعض القدرات من جانب المتصفح عن طريق إدراج نصوص جافا سكريبت في المستند HTML الذي يتم تحميله بواسطة المتصفح. ويتحقق هذا المستند من صحة معلمات النموذج.
  • الإصدار 3: نسمح للخدمة بتذكر المحاكاة المختلفة التي أجراها العميل من خلال إدارة جلسة عمل. يتم تعديل واجهة HTML وفقًا لذلك لعرض هذه المحاكاة.
  • الإصدار 4: أصبح العميل الآن تطبيقًا مستقلًا يعمل على وحدة التحكم. وهذا يتيح لنا العودة إلى كتابة برامج العملاء على الويب.

في هذه المرحلة، يمكننا إبداء بعض الملاحظات:

  • تسمح الإصدارات من 1 إلى 3 باستخدام متصفحات لا تمتلك أي قدرات أخرى سوى القدرة على تنفيذ نصوص جافا سكريبت. تجدر الإشارة إلى أن المستخدم لديه دائمًا إمكانية منع تنفيذ هذه البرامج النصية. عندئذٍ لن يعمل التطبيق إلا جزئيًا في الإصدار 1 (لن تعمل خيار "حذف") ولن يعمل على الإطلاق في الإصدارين 2 و3 (لن تعمل خيارات "حذف" و"حساب"). قد يكون من المفيد توفير إصدار من الخدمة لا يستخدم نصوص جافا سكريبت.

عند كتابة خدمة ويب، يجب أن نسأل أنفسنا عن أنواع العملاء التي نستهدفها. إذا أردنا استهداف أكبر عدد ممكن من العملاء، فسنكتب تطبيقًا لا يرسل إلى المتصفحات سوى HTML (بدون جافا سكريبت أو أبلت). أما إذا كنا نعمل ضمن شبكة إنترانت ونتحكم في تكوين أجهزة المستخدمين فيها، فيمكننا عندئذٍ أن نكون أكثر صرامة فيما يتعلق بمتطلبات العميل.

الإصدار 4 هو عميل ويب يستخرج المعلومات التي يحتاجها من تدفق HTML المرسل من الخادم. في كثير من الأحيان، لا نتحكم في هذا التدفق. هذا هو الحال عندما نكتب عميلاً لخدمة ويب موجودة على الشبكة ويديرها شخص آخر. لنأخذ مثالاً. لنفترض أن خدمة محاكاة حساب الضرائب الخاصة بنا قد تمت برمجتها بواسطة شركة X. حالياً، ترسل الخدمة المحاكاة في جدول HTML، ويستفيد عميلنا من هذا الأمر لاستردادها. وبذلك، يقارن كل سطر من رد الخادم بالتعبير النمطي:

     // modèle d'une ligne du tableau des simulations
     // <tr><td>oui</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. وبفضل ورقة الأنماط، ستعرض المتصفحات الشكل المرئي الجديد ولن يتعين تعديل عملاء الويب المبرمجين.