Skip to content

11. تمرین IMPOTS با سرویس WEB و معماری سه‌لایه

ما تمرین IMPOTS (رجوع کنید به بندهای 4.2، 4.3 و 6) را مجدداً بررسی کرده و آن را به یک برنامهٔ کلاینت/سرور تبدیل خواهیم کرد. اسکریپت سرور به سه مؤلفه تقسیم خواهد شد:

  • یک لایه به نام [dao] (ابجکت‌های دسترسی به داده)، که ارتباط با پایگاه داده MySQL را مدیریت خواهد کرد
  • یک لایه به نام [métier] که مالیات را محاسبه خواهد کرد
  • یک لایه به نام [web] که مسئول ارتباط با کلاینت‌های وب خواهد بود.

اسکریپت کلاینت [1]:

  • اطلاعات سه گانه ($marié, $enfants, $salaire) مورد نیاز برای محاسبه مالیات را به اسکریپت سرور ارسال می‌کند
  • پاسخ سرور را در کنسول نمایش می‌دهد

اسکریپت سرور [2] شامل لایه [web] سرور است.

  • در آغاز یک جلسهٔ جدید مشتری، داده‌های پایگاه‌های دادهٔ MySQL و [dbimpots] را در آرایه‌ها قرار خواهد داد. برای این کار، از لایهٔ [dao] فراخوانی خواهد کرد. آرایه‌هایی که به این صورت ایجاد می‌شوند، در جلسهٔ مشتری قرار داده خواهند شد تا در درخواست‌های بعدی مشتری قابل استفاده باشند.
  • هنگامی که یک کلاینت درخواستی را ارسال می‌کند، سه مورد اطلاعات را ارسال خواهد کرد ($marié, $enfants, $salaire) را به لایه [métier] ارسال می‌کند که مالیات $impot را محاسبه خواهد کرد.
  • اسکریپت سرور مالیات محاسبه شده $impôt را بازخواهد گرداند.

11.1. اسکریپت کلاینت (clients_impots_05_web)

اسکریپت کلاینت به‌عنوان کلاینت سرویس وب محاسبه مالیات عمل خواهد کرد. این اسکریپت پارامترها را (POST) به سرور در قالب زیر ارسال می‌کند:

params=$married,$children,$salary که

  • $marié عبارت خواهد بود از رشته oui یا non،
  • $enfants تعداد فرزندان خواهد بود،
  • $salaire حقوق مالیات‌دهنده است

این پارامترها را از یک فایل متنی به نام [data.txt] در قالب (متأهل، تعداد فرزندان، حقوق) بازیابی می‌کند:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

اسکریپت کلاینت

  • فایل متنی [data.txt] را خط به خط می‌خواند
  • ارسال رشته `params=$marié,$enfants,$salaire` به سرویس وب محاسبه مالیات
  • پاسخ را از سرویس دریافت می‌کند. این پاسخ ممکن است یکی از دو شکل زیر را داشته باشد:
<erreur>message d'erreur</erreur>
<impot>montant de l'impôt</impot>
  • پاسخ سرور را در یک فایل متنی به نام [resultats.txt] در یکی از دو فرمت زیر ذخیره می‌کند:
marié:enfants:salaire:erreur
marié:enfants:salaire:impôt

کد اسکریپت کلاینت به شرح زیر است:


<?php

// مالیات مشتری
// مدیریت خطا
ini_set("display_errors", "off");

// ---------------------------------------------------------------------------------
// یک کلاس از توابع کمکی
class Utilitaires {

  function cutNewLinechar($ligne) {
    ...
  }

}

// main -----------------------------------------------------
// تعریف ثابت‌ها
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
// داده‌های سرور
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/impots_05_web.php";

//جزئیات اشخاص مشمول مالیات (وضعیت تأهل، تعداد فرزندان، حقوق سالانه)
//در فایل متنی $DATA قرار داده شده‌اند، به طوری که برای هر مودی یک خط در نظر گرفته شده است
//نتایج (وضعیت تأهل، تعداد فرزندان، حقوق سالانه، مالیات پرداختی) 
// یا (وضعیت تأهل، تعداد فرزندان، حقوق سالانه، پیام خطا) در قرار داده شده‌اند
// فایل متنی $RESULTATS، با یک نتیجه در هر خط

//کلاس ابزارها
$u = new Utilitaires();

// باز کردن فایل داده‌های مالیات‌دهنده
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}

// باز کردن فایل نتایج
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
  print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
  exit;
}

// پردازش خط جاری فایل داده‌های مالیات‌دهنده
while ($ligne = fgets($data, 100)) {
  // حذف هر کاراکتر پایان خط
  $ligne = $u->cutNewLineChar($ligne);
  //بازیابی سه فیلد «married:children:salary» که $ligne را تشکیل می‌دهند
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  //مالیات محاسبه می‌شود
  list($erreur, $impôt) = calculerImpot($HOTE, $PORT, $urlServeur, $cookie, array($marié, $enfants, $salaire));
  //نتیجه وارد می‌شود
  $résultat = $erreur ? "$marié:$enfants:$salaire:$erreur" : "$marié:$enfants:$salaire:$impôt";
  fputs($résultats, "$résultat\n");
  // رکورد بعدی
}
// بستن فایل‌ها
fclose($data);
fclose($résultats);

//پایان
print "Terminé...\n";
exit;

function calculerImpot($HOTE, $PORT, $urlServeur, &$cookie, $params) {
  // مشتری را به ($HOTE,$PORT,$urlServeur) متصل می‌کند
  //کوکی $cookie را ارسال می‌کند اگر خالی نباشد. $cookie به صورت ارجاعی ارسال می‌شود.
  // کوکی $params را به سرور ارسال می‌کند
  //خط تکی بازگردانده شده توسط سرور را پردازش می‌کند
  
  //یک اتصال روی پورت ۸۰ $HOTE باز می‌کند
  $connexion = fsockopen($HOTE, $PORT);
  //خطا؟
  if (!$connexion)
    return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
  // سربرگ‌های پروتکل HTTP باید با یک خط خالی پایان یابند
  //POST
  fputs($connexion, "POST $urlServeur HTTP/1.1\n");
  // میزبان
  fputs($connexion, "Host: localhost\n");
  // اتصال
  fputs($connexion, "Connection: close\n");
// کوکی ارسال می‌شود اگر خالی نباشد
  if ($cookie) {
    fputs($connexion, "Cookie: $cookie\n");
  }//اگر
  // اکنون دستور کلاینت پس از رمزگذاری ارسال می‌شود
  $infos = "params=" . urlencode(implode(",", $params));
  // نوع اطلاعات ارسالی را مشخص کنید
  fputs($connexion, "Content-type: application/x-www-form-urlencoded\n");
  // اندازه (تعداد کاراکترها) اطلاعات ارسالی ارسال می‌شود
  fputs($connexion, "Content-length: " . strlen($infos) . "\n");
  // ما یک خط خالی ارسال می‌کنیم
  fputs($connexion, "\n");
  //ارسال اطلاعات
  fputs($connexion, $infos);
  //پاسخ وب‌سرور نمایش داده می‌شود
  //و اطمینان از بازیابی هر کوکی
  while ($ligne = fgets($connexion, 1000)) {
    //کوکی – فقط در اولین پاسخ
    if (!$cookie) {
      if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
        $cookie = $champs[1];
      }//اگر
    }
    //به محض اینکه یک خط خالی وجود داشته باشد، پاسخ HTTP کامل است
    if (trim($ligne) == "") {
      break;
    }
  }//در حالی که
  //یک خط از نتیجه را می‌خواند
  $ligne = fgets($connexion, 1000);
  //اتصال را ببندید
  fclose($connexion);
  //محاسبه نتیجه
  $erreur="";
  $impôt="";
  if (preg_match("/^<erreur>(.*?)<\/erreur>\s*$/", $ligne, $champs)) {
    $erreur = $champs[1];
  } else {
    if (preg_match("/^<impot>(.*?)<\/impot>\s*$/", $ligne, $champs)) {
      $impôt = $champs[1];
    }else{
      $erreur="résultat du serveur non exploitable";
    }
  }
  //بازگشت
  return array($erreur, $impôt);
}

نظرات

کد اسکریپت سمت کلاینت شامل عناصری است که قبلاً دیده‌ایم:

  • خطوط ۹–۱۵: کلاس [Utilitaires] در نسخهٔ ۳، بند ۶ معرفی شد
  • خطوط 17–68: برنامهٔ اصلی مشابه نسخهٔ 1، بند 4.2 است. تنها در محاسبهٔ مالیات در خط 56 تفاوت دارد.
  • خط ۵۶: تابع محاسبه مالیات پارامترهای زیر را می‌پذیرد:
    • $HOTE، $PORT، $urlServeur: این‌ها برای اتصال به سرویس وب استفاده می‌شوند
    • $cookie: این کوکی جلسه است. این پارامتر به صورت ارجاعی ارسال می‌شود. مقدار آن توسط تابع محاسبه مالیات تعیین می‌شود. در اولین فراخوانی، هیچ مقداری ندارد. پس از آن، مقدار خواهد داشت.
    • array($marié, $enfants, $salaire): یک خط در فایل [data.txt] را نشان می‌دهد

تابع محاسبه مالیات یک آرایه از دو نتیجه را بازمی‌گرداند ($erreur, $impôt)، که در آن $erreur یک پیام خطای احتمالی و $impôt مبلغ مالیات است.

  • خطوط ۷۰–۱۳۴: این یک کلاینت استاندارد HTTP است، از آن نوع که بارها با آن مواجه شده‌ایم. نکات زیر را توجه کنید:
  • خط ۸۳: پارامترها ($marié, $enfants, $salaire) از طریق یک POST به سرور ارسال می‌شوند
  • خطوط ۸۹–۹۱: اگر کلاینت شناسهٔ جلسه داشته باشد، آن را برای سرور ارسال می‌کند
  • خط ۹۳: ایجاد پارامتر params
  • خط ۱۰۱: پارامتر params ارسال می‌شود
  • خطوط ۱۰۴–۱۱۵: کلاینت تمام هدرهای HTTP ارسال‌شده توسط سرور را می‌خواند تا زمانی که به خط خالی علامت‌گذار پایان هدرها برخورد کند. از این فرصت استفاده می‌کند تا شناسه جلسه را از پاسخ به اولین درخواست خود بازیابی کند.
  • خطوط ۱۲۳–۱۲۵: ما هر خط از قالب <erreur>message</erreur> را پردازش می‌کنیم
  • خطوط ۱۲۶–۱۲۸: همین کار برای هر خطی با قالب <impot>montant</impot> انجام می‌شود
  • خط ۱۳۳: نتیجه بازگردانده می‌شود

11.2. سرویس وب محاسبه مالیات

در اینجا به سه اسکریپتی که سرور را تشکیل می‌دهند، می‌پردازیم:

پروژهٔ متناظر NetBeans به شرح زیر است:

در [1]، سرور از اسکریپت‌های زیر PHP تشکیل شده است:

  • [impots_05_entites] شامل کلاس‌های مورد استفاده توسط سرور است
  • [impots_05_dao] شامل کلاس‌ها و رابط‌های لایه [dao] است
  • [impots_05_metier] شامل کلاس‌ها و رابط‌های لایه [metier] است
  • [impots_05_web] شامل کلاس‌ها و رابط‌های لایه [dao] است

ما با معرفی دو کلاس که در لایه‌های مختلف سرویس وب استفاده می‌شوند، شروع می‌کنیم.

11.2.1. اشیاء سرویس وب (impots_05_entites)

پایگاه داده MySQL [dbimpots] شامل جدولی به نام [impots] است که داده‌های مورد نیاز برای محاسبه مالیات [1] را در خود نگهداری می‌کند:

ما داده‌های جداول MySQL و [impots] را در یک آرایهٔ شیء Tranche ذخیره خواهیم کرد، که در آن Tranche کلاس زیر است:


<?php

// یک طبقه مالیاتی
class Tranche {

  // fields خصوصی
  private $limite;
  private $coeffR;
  private $coeffN;

  // گیرنده و تنظیم‌کننده
  public function getLimite() {
    return $this->limite;
  }

  public function setLimite($limite) {
    $this->limite = $limite;
  }

  public function getCoeffR() {
    return $this->coeffR;
  }

  public function setCoeffR($coeffR) {
    $this->coeffR = $coeffR;
  }

  public function getCoeffN() {
    return $this->coeffN;
  }

  public function setCoeffN($coeffN) {
    $this->coeffN = $coeffN;
  }

  // سازنده
  public function __construct($limite, $coeffR, $coeffN) {
    $this->setLimite($limite);
    $this->setCoeffR($coeffR);
    $this->setCoeffN($coeffN);
  }

  // toString
  public function __toString(){
    return "[$this->limite,$this->coeffR,$this->coeffN]";
  }
}

fields خصوصی [$limite, $coeffR, $coeffN] برای ذخیره ستون‌های [limites, coeffR, coeffN] از یک سطر در جدول MySQL [impots] استفاده خواهند شد.

علاوه بر این، کد سرور از استثنای اختصاصی خود، کلاس ImpotsException استفاده خواهد کرد:

1
2
3
4
5
6
7
class ImpotsException extends Exception {

  public function __construct($message, $code=0) {
    parent::__construct($message, $code);
  }

}
  • خط ۱: کلاس [ImpotsException] از کلاس [Exception] که در PHP 5 پیش‌تعریف شده است، ارث می‌برد
  • خط ۳: سازنده کلاس [ImpotsException] دو پارامتر می‌گیرد:
    • $message: یک پیام خطا
    • $code: یک کد خطا

11.2.2. لایه [dao] (impots_05_dao)

لایه [dao] دسترسی به داده‌های پایگاه داده را فراهم می‌کند:

لایه [dao] دارای رابط زیر است:

1
2
3
4
5
6
// رابط DAO
interface IImpotsDao {

// یک آرایه از اشیاء Tranche را بازمی‌گرداند
  function getData();
}

رابط IImpotsDao تنها تابع getData را در دسترس قرار می‌دهد. این تابع سطرهای مختلف جدول MySQL [dbimpots.impots] را در آرایه‌ای از اشیاء Tranche قرار می‌دهد.

کلاس پیاده‌سازی به شرح زیر است:


<?php

//لایه DAO
//وابستگی‌ها
require_once "impots_05_entites.php";
// ثوابت
define("TABLE", "impots");

// -----------------------------------------------------------------
//پیاده‌سازی انتزاعی
abstract class ImpotsDaoWithPdo implements IImpotsDao {

// fields خصوصی
  private $dsn;
  private $user;
  private $passwd;
  private $tranches;

// گیرنده و تنظیم‌کننده
  public function getDsn() {
    return $this->dsn;
  }

  public function setDsn($dsn) {
    $this->dsn = $dsn;
  }

  public function getUser() {
    return $this->user;
  }

  public function setUser($user) {
    $this->user = $user;
  }

  public function getPasswd() {
    return $this->passwd;
  }

  public function setPasswd($passwd) {
    $this->passwd = $passwd;
  }

  // سازنده
  public function __construct($dsn, $user, $passwd) {
    // ما پارامترها را ذخیره می‌کنیم
    $this->setDsn($dsn);
    $this->setUser($user);
    $this->setPasswd($passwd);
    //بازیابی داده‌ها از SGBD
    // اتصال برقرار می‌کند ($user, $pwd) به پایگاه داده $dsn
    try {
      //اتصال
      $connexion = new PDO($dsn, $user, $passwd, array(PDO::ATTR_PERSISTENT => true));
      //از جدول $TABLE می‌خواند
      $requête = "select limites,coeffR,coeffN from " . TABLE;
      //پرس‌وجوی $requête را روی اتصال $connexion اجرا می‌کند
      $statement = $connexion->prepare($requête);
      $statement->execute();
      //پردازش نتیجه پرس‌وجو
      while ($colonnes = $statement->fetch()) {
        $this->tranches[] = new Tranche($colonnes[0], $colonnes[1], $colonnes[2]);
      }
      //اتصال را قطع می‌کند
      $connexion=NULL;
    } catch (PDOException $e) {
      //با خطا بازمی‌گردد
      throw new ImpotsException($e->getMessage(), 1);
    }
  }

  public function getData(){
    return $this->tranches;
  }
}
  • خط ۵: پیاده‌سازی رابط [IImpotsDao] نیازمند کلاس‌های تعریف‌شده در اسکریپت [impots_05_entites] است.
  • خط ۱۱: تعریف یک کلاس انتزاعی. کلاس انتزاعی کلاسی است که نمی‌توان آن را نمونه‌سازی کرد. برای نمونه‌سازی یک کلاس انتزاعی، باید از آن ارث‌بری شود. یک کلاس ممکن است انتزاعی اعلام شود، یا به این دلیل که نمی‌توان آن را نمونه‌سازی کرد (برخی از متدهای آن تعریف نشده‌اند) یا به این دلیل که قصد نمونه‌سازی آن وجود ندارد. در این مورد، کلاس [ImpotsDaoWithPdo] قرار نیست نمونه‌سازی شود. در عوض، کلاس‌های مشتق‌شده نمونه‌سازی خواهند شد.
  • خط ۱۱: کلاس [ImpotsDaoWithPdo] رابط [IImpotsDao] را پیاده‌سازی می‌کند. بنابراین باید متد getData را تعریف کند. این متد را می‌توان در خطوط ۷۲–۷۴ یافت.
  • خط ۱۴: $dsn (نام منبع داده) یک رشتهٔ متنی است که به‌طور منحصربه‌فرد SGBD و پایگاه‌دادهٔ مورد استفاده را شناسایی می‌کند.
  • خط ۱۵: $user کاربر در حال ورود به پایگاه داده را شناسایی می‌کند
  • خط 16: $passwd رمز عبور کاربر قبلی است
  • خط ۱۷: $tranches آرایهٔ اشیاء Tranche است که در آن جداول MySQL و [dbimpots.impots] ذخیره خواهند شد.
  • خطوط ۴۵–۷۰: سازنده کلاس. این کد قبلاً در نسخه ۴، بخش ۸.۲ دیده شده است. توجه داشته باشید که ساخت شی [ImpotsDaoWithPdo] ممکن است ناموفق باشد. در این صورت یک استثنا از نوع [ImpotsException] پرتاب می‌شود.
  • خطوط ۷۲–۷۴: متد [getData] از رابط [IImpotsDao].

کلاس [ImpotsDaoWithPdo] برای هر SGBD مناسب است. سازنده کلاس، در خط ۴۵، نیاز دارد که نام منبع داده پایگاه داده مشخص باشد. این رشته کاراکتری به SGBD مورد استفاده بستگی دارد. ما تصمیم گرفته‌ایم که کاربر کلاس نیازی به دانستن این نام منبع داده نداشته باشد. برای هر SGBD، یک کلاس خاص مشتق‌شده از [ImpotsDaoWithPdo] وجود خواهد داشت. برای SGBD و MySQL، این کلاس زیر خواهد بود:


class ImpotsDaoWithMySQL extends ImpotsDaoWithPdo {

  public function __construct($host, $port, $base, $user, $passwd) {
    parent::__construct("mysql:host=$host;dbname=$base;port=$port", $user, $passwd);
  }

}
  • در خط ۳، سازنده به نام منبع داده نیازی ندارد، بلکه صرفاً نام ماشین میزبان برای SGBD ($host)، پورت گوش‌دادن آن ($port) و نام پایگاه داده ($base).
  • در خط ۴، نام منبع داده برای پایگاه داده MySQL ساخته شده و برای فراخوانی سازنده کلاس والد استفاده می‌شود.

شایان ذکر است که برای سازگار شدن با یک SGBD متفاوت، کافی است کلاس مناسبی مشتق شده از [ImpotsDaoWithPdo] نوشته شود. در هر مورد، نام منبع داده خاص مربوط به SGBD مورد استفاده باید ساخته شود.

11.2.3. لایه [métier] (impots_05_metier)

لایه [metier] شامل منطق محاسبه مالیات است:

لایه [métier] دارای رابط زیر است:


<?php

//رابط کسب‌وکار
interface IImpotsMetier {
  public function calculerImpot($marié, $enfants, $salaire);
}

رابط [IImpotsMetier] تنها یک متد را ارائه می‌دهد، متد [calculerImpot]، که مالیات مودی را بر اساس پارامترهای زیر محاسبه می‌کند:

  • $marié: یک رشتهٔ «بله» یا «خیر» بسته به اینکه مودی متأهل باشد یا خیر
  • $enfants: تعداد فرزندان مؤدی
  • $salaire: حقوق آنها

لایه [web] این پارامترها را فراهم می‌کند.

پیاده‌سازی رابط [IImpotsMetier] به شرح زیر است:


// وابستگی‌ها
require_once "impots_05_dao.php";

// ------------------------------------------------------------------
//کلاس پیاده‌سازی
class ImpotsMetier implements IImpotsMetier {

  // لایه DAO
  private $dao;
   //آرایهٔ اشیاء [Tranche]
  private $data;

  // گیرنده و تنظیم‌کننده
  public function getDao() {
    return $this->dao;
  }

  public function setDao($dao) {
    $this->dao = $dao;
  }

  public function setData($data){
    $this->data=$data;
  }
  
  public function __construct($dao) {
    // داده‌های مورد نیاز برای محاسبه مالیات را بازیابی می‌کند
    $this->setDao($dao);
    $this->setData($this->dao->getData());
  }

  public function calculerImpot($marié, $enfants, $salaire) {
    //$marié: بله، نه
    // $enfants: تعداد فرزندان
    //$salaire: حقوق سالانه
    // تعداد سهام
    $marié = strtolower($marié);
    if ($marié == "oui")
      $nbParts = $enfants / 2 + 2;
    else
      $nbParts=$enfants / 2 + 1;
    // نیم سهم اضافی در صورت وجود حداقل ۳ فرزند
    if ($enfants >= 3)
      $nbParts+=0.5;
    // درآمد مشمول مالیات
    $revenuImposable = 0.72 * $salaire;
    // ضریب خانوادگی
    $quotient = $revenuImposable / $nbParts;
    //در انتهای جدول محدودیت‌ها قرار می‌گیرد تا حلقهٔ زیرین متوقف شود
    $N = count($this->data);
    $this->data[$N - 1]->setLimite($quotient);
    //محاسبه مالیات
    $i = 0;
    while ($i < $N and $quotient > $this->data[$i]->getLimite()) {
      $i++;
    }
    // زیرا $quotient در انتهای جدول $limites قرار گرفته است، حلقه قبلی
    // نمی‌تواند از آرایه $limites فراتر رود
    //اکنون می‌توانیم مالیات را محاسبه کنیم
    return floor($revenuImposable * $this->data[$i]->getCoeffR() - $nbParts * $this->data[$i]->getCoeffN());
  }

}
  • خط ۲: لایه [métier] به کلاس‌های لایه [dao] و اشیاء (Tranche, ImpotsException) نیاز دارد.
  • خط ۶: کلاس [ImpotsMetier] رابط [IimpotsMetier] را پیاده‌سازی می‌کند.
  • خطوط ۹–۱۱: فیلدهای خصوصی کلاس:
    • $dao: ارجاع به لایه [dao]
    • $data: آرایه‌ای از اشیاء از نوع [Tranche] که توسط لایه [dao] فراهم شده است
  • خطوط ۲۶–۳۰: سازنده کلاس دو فیلد قبلی را مقداردهی اولیه می‌کند. این متد یک ارجاع به لایه [dao] را به عنوان پارامتر دریافت می‌کند.
  • خطوط ۳۲–۶۱: پیاده‌سازی متد [calculerImpot] از رابط [IimpotsMetier]. این متد از نسخهٔ ۱ (بخش ۴.۲) موجود است.

11.2.4. لایه [web] (impots_05_web)

لایه [metier] شامل منطق محاسبه مالیات است:

لایه [web] شامل سرویس وبی است که به کلاینت‌های وب پاسخ می‌دهد. باید توجه داشت که این کلاینت‌ها با ارسال پارامتر زیر به سرویس وب درخواست می‌دهند: params=marié,enfants,salaire. این یک سرویس وب از نوع توصیف‌شده در پاراگراف‌های قبلی است. کد آن به شرح زیر است:


<?php

//لایه منطق کسب‌وکار
require_once "impots_05_metier.php";

// مدیریت خطا
ini_set("display_errors", "off");

// سربرگ UTF-8
header("Content-Type: text/plain; charset=utf-8");

// ------------------------------------------------------------------------------
// وب سرویس مالیات
// تعریف ثابت‌ها
$HOTE = "localhost";
$PORT = 3306;
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
//داده‌های مورد نیاز برای محاسبه مالیات در جدول MySQL IMPOTS قرار داده شده‌اند
//متعلق به پایگاه داده $BASE است. جدول دارای ساختار زیر است
//limits decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
//پارامترهای اشخاص مشمول مالیات (وضعیت تأهل، تعداد فرزندان، حقوق سالانه)
// توسط مشتری در قالب پارامترها=وضعیت تأهل، تعداد فرزندان، حقوق سالانه ارسال می‌شوند
//نتایج (وضعیت تأهل، تعداد فرزندان، حقوق سالانه، مالیات قابل پرداخت) به مشتری بازگردانده می‌شوند
// در قالب <tax>مالیات</tax>
//یا به شکل <error>error</error>، اگر پارامترها نامعتبر باشند

//لایه [métier] در جلسه بازیابی می‌شود
session_start();
if (!isset($_SESSION['metier'])) {
// نمونهٔ لایهٔ [dao] و لایهٔ [métier]
  try {
    $_SESSION['metier'] = new ImpotsMetier(new ImpotsDaoWithMySQL($HOTE, $PORT, $BASE, $USER, $PWD));
  } catch (ImpotsException $ie) {
    print "<erreur>Erreur : " . utf8_encode($ie->getMessage() . "</erreur>");
    exit;
  }
}
$metier = $_SESSION['metier'];

// خط ارسال‌شده توسط کلاینت بازیابی می‌شود
$params = utf8_encode(htmlspecialchars(strtolower(trim($_POST['params']))));
$items = explode(",", $params);
//باید تنها ۳ پارامتر وجود داشته باشد
if (count($items) != 3) {
  print "<erreur>[$params] : nombre de paramètres invalides</erreur>\n";
  exit;
}//اگر
//پارامتر اول (وضعیت تأهل) باید بله/خیر باشد
$marié = trim($items[0]);
if ($marié != "oui" and $marié != "non") {
  print "<erreur>[$params] : 1er paramètre invalide</erreur>\n";
  exit;
}//اگر
// پارامتر دوم (تعداد فرزندان) باید یک عدد صحیح باشد
if (!preg_match("/^\s*(\d+)\s*$/", $items[1], $champs)) {
  print "<erreur>[$params] : 2ième paramètre invalide</erreur>\n";
  exit;
}//اگر
$enfants = $champs[1];
//پارامتر سوم (حقوق) باید یک عدد صحیح باشد
if (!preg_match("/^\s*(\d+)\s*$/", $items[2], $champs)) {
  print "<erreur>[$params] : 3ième paramètre invalide</erreur>\n";
  exit;
}//اگر
$salaire = $champs[1];
// مالیات محاسبه می‌شود
$impôt = $metier->calculerImpot($marié, $enfants, $salaire);
// نتیجه را بازگردانید
print "<impot>$impôt</impot>\n";
//پایان
exit;
  • خط ۴: لایه [web] به کلاس‌های لایه [métier] نیاز دارد
  • خطوط ۳۰–۴۰: ارجاع به لایه [métier] به جلسه اضافه می‌شود. اگر به یاد بیاوریم که این لایه [métier] به لایه [dao] ارجاع می‌دهد و لایه دوم داده‌های SGBD را ذخیره می‌کند، می‌توانیم ببینیم که:
    • اولین درخواست کلاینت دسترسی به SGBD را تحریک خواهد کرد
    • که درخواست‌های بعدی از همان کلاینت از داده‌های ذخیره‌شده توسط لایه [dao] استفاده خواهند کرد. بنابراین هیچ دسترسی به SGBD وجود ندارد.
  • خط ۳۴: ایجاد یک لایه [métier] که با یک لایه [dao] پیاده‌سازی‌شده برای SGBD MySQL کار می‌کند.
  • خطوط ۳۵–۳۷: رسیدگی به هرگونه خطا در عملیات قبلی. در این مورد، یک خط <erreur>message</erreur> به کلاینت ارسال می‌شود.
  • خط ۴۳: پارامتر 'params' که توسط کلاینت ارسال شده است، بازیابی می‌شود.
  • خطوط ۴۶–۴۹: تعداد آیتم‌های یافت‌شده در 'params' بررسی می‌شود
  • خطوط ۵۱–۵۵: اعتبار اولین بخش اطلاعات بررسی می‌شود
  • خطوط ۵۶–۶۰: همین امر در مورد بخش دوم اطلاعات نیز صدق می‌کند
  • خطوط ۶۲–۶۶: همین امر در مورد سومین بخش اطلاعات نیز صدق می‌کند
  • خط ۶۹: لایه [métier] مالیات را محاسبه می‌کند.
  • خط ۷۱: نتیجه به کلاینت ارسال می‌شود

نتایج

به یاد داشته باشید که کلاینت [client_impots_web_05] فایل زیر را پردازش می‌کند: [data.txt]:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

با استفاده از این خطوط (وضعیت تأهل، فرزندان، حقوق)، مشتری به سرور محاسبه مالیات درخواست می‌دهد و نتایج را در فایل متنی [resultats.txt] می‌نویسد. پس از اجرای مشتری، محتویات این فایل به شرح زیر است:

oui:2:200000:22504
non:2:200000:33388
oui:3:200000:16400
non:3:200000:22504
oui:5:50000:0
non:0:3000000:1354938

که در آن هر خط به شکل (متأهل، فرزندان، حقوق، مالیات محاسبه‌شده) است.