Skip to content

8. تمرین عملی – نسخه ۳

ما به تمرینی که قبلاً پوشش داده شده (بخش‌های 4.3 و 4.4) بازمی‌گردیم تا آن را با استفاده از کد PHP که از یک کلاس بهره می‌برد، حل کنیم.

8.1. درخت اسکریپت

Image

8.2. استثنای [ExceptionImpots]

در نسخهٔ ۰۳، هنگامی که سازندهٔ کلاس یا متد با خطایی مواجه می‌شود، یک استثنای [ExceptionImpots] به شرح زیر پرتاب خواهد شد:

<?php

// فضای نام
namespace Application;

class ExceptionImpots extends \RuntimeException {

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

}

توضیحات

  • خط ۴: کلاس [ExceptionImpots] در فضای نام [Application] قرار دارد؛
  • خط ۶: کلاس [ExceptionImpots] از کلاس پیش‌تعریف‌شده در PHP [RuntimeException] ارث می‌برد؛
  • خط ۸: سازنده انتظار دو پارامتر را دارد:
    • $message: پیام خطا مربوط به استثنا است؛
    • $code: کد خطا مربوط به استثنا است. اگر این کد موجود نباشد، از کد 0 استفاده خواهد شد؛

8.3. کلاس [TaxAdminData]

در نسخه ۰۲، داده‌های سازمان مالیاتی یکپارچه شده است:

  • ابتدا در فایلی به نام jSON؛
  • سپس از این فایل، jSON، به یک آرایهٔ asociative منتقل می‌شود؛

در نسخه 03، داده‌های مرجع مالیاتی همچنان در فایل [taxadmindata.json] قرار دارد اما با نام‌های ویژگی متفاوت:


{
    "limites": [
        9964,
        27519,
        73779,
        156244,
        0
    ],
    "coeffR": [
        0,
        0.14,
        0.3,
        0.41,
        0.45
    ],
    "coeffN": [
        0,
        1394.96,
        5798,
        13913.69,
        20163.45
    ],
    "plafondQfDemiPart": 1551,
    "plafondRevenusCelibatairePourReduction": 21037,
    "plafondRevenusCouplePourReduction": 42074,
    "valeurReducDemiPart": 3797,
    "plafondDecoteCelibataire": 1196,
    "plafondDecoteCouple": 1970,
    "plafondImpotCouplePourDecote": 2627,
    "plafondImpotCelibatairePourDecote": 1595,
    "abattementDixPourcentMax": 12502,
    "abattementDixPourcentMin": 437
}

در نسخه 02، این فایل برای مقداردهی اولیه یک آرایه انجمنی استفاده می‌شد. در نسخه 03، این فایل کلاس زیر را مقداردهی اولیه می‌کند: [TaxAdminData]:


<?php

namespace Application;

class TaxAdminData {
  // باندهای مالیاتی
  private $limites;
  private $coeffR;
  private $coeffN;
  // ثوابت محاسبه مالیات
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;

  // ابتدایی‌سازی
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // بازیابی محتویات فایل داده‌های مالیاتی
    $fileContents = \file_get_contents($taxAdminDataFilename);
    $erreur = FALSE;
    // خطا؟
    if (!$fileContents) {
      // ثبت خطا
      $erreur = TRUE;
      $message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
    }
    if (!$erreur) {
      // بازیابی کد jSON از فایل پیکربندی در یک آرایهٔ asociative
      $arrayTaxAdminData = \json_decode($fileContents, true);
      //خطا؟
      if ($arrayTaxAdminData === FALSE) {
        //ثبت خطا
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
      }
    }
    //خطا؟
    if ($erreur) {
      //یک استثنا پرتاب می‌شود
      throw new ExceptionImpots($message);
    }
    // ویژگی‌های کلاس را مقداردهی اولیه می‌کنیم
    foreach ($arrayTaxAdminData as $key => $value) {
      $this->$key = $value;
    }
    //بررسی کنید که همه کلیدها مقداردهی شده‌اند
    $arrayOfAttributes = \get_object_vars($this);
    foreach ($arrayOfAttributes as $key => $value) {
      if (!isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de [TaxAdminData] n'a pas été initialisé");
      }
    }
    // بررسی می‌کنیم که همه مقادیر اعداد حقیقی هستند
    foreach ($this as $key => $value) {
      // $value باید یک عدد حقیقی ≥ 0 یا یک آرایه از اعداد حقیقی ≥ 0 باشد
      $result = $this->check($value);
      // خطا؟
      if ($result->erreur) {
        //یک استثنا پرتاب می‌شود
        throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
      } else {
        //مقدار ثبت می‌شود
        $this->$key = $result->value;
      }
    }
    // شیء بازگردانده می‌شود
    return $this;
  }

  private function check($value): \stdClass {

    return $result;
  }

    //toString
  public function __toString() {
    // رشته JSON شیء
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

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

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


  }

  public function setLimites($limites) {
    $this->limites = $limites;
    return $this;
  }

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



}

نظرات

  • خطوط ۶–۲۰: ویژگی‌هایی که ویژگی‌های هم‌نام را در فایل‌های jSON و [taxadmindata.json] در خود نگه می‌دارند. این یک نکته مهم است: ویژگی‌های کلاس [TaxAdminData] با ویژگی‌های موجود در فایل‌های jSON و [taxadmindata.json] یکسان هستند. این ویژگی نوشتن کد را بسیار آسان‌تر می‌کند؛
  • کلاس [TaxAdminData] فاقد سازنده است. در PHP، داشتن چندین سازنده امکان‌پذیر نیست. بنابراین، تعریف یک سازنده مانع از این می‌شود که شیء به هیچ روش دیگری مقداردهی اولیه شود. در ادامه، کلاس‌های ما فاقد کانتراکتور خواهند بود اما چندین متد از نوع [setFromQqChose] خواهند داشت که امکان инициализация آنها به روش‌های مختلف را فراهم می‌کند. بنابراین یک شیء از نوع [TaxAdminData] با استفاده از عبارت زیر ساخته می‌شود:
(new TaxAdminData())→setFromQqChose(…)
  • خط ۲۳: متد [setFromJsonFile] ویژگی‌های کلاس را با موارد هم‌نام در فایل [$jsonFilename] مقداردهی اولیه می‌کند؛
  • خطوط ۲۴–۴۲: فایل jSON برای ساخت آرایهٔ asociative [$arrayTaxAdminData] استفاده می‌شود. ما قبلاً این کد را در اسکریپت [main.php] از نسخهٔ ۰۲ دیده‌ایم؛
  • خطوط 44–47: اگر هنگام پردازش فایل jSON خطایی رخ دهد، یک استثنا پرتاب می‌شود. این استثنا تا اسکریپت اصلی [main.php] منتقل خواهد شد؛
  • خطوط ۴۸–۵۱: ویژگی‌های کلاس مقداردهی اولیه می‌شوند. در اینجا، ما از این واقعیت استفاده می‌کنیم که آرایهٔ asociative [$arrayTaxAdminData] و کلاس [TaxAdminData] دارای ویژگی‌هایی با نام‌های یکسان با مقادیر موجود در فایل jSON هستند؛
  • خطوط ۵۳–۵۷: بررسی می‌کنیم که تمام ویژگی‌های کلاس [TaxAdminData] مقداردهی شده‌اند؛
  • خط ۵۳: عبارت [get_object_vars($this)] یک آرایهٔ انجمنی را بازمی‌گرداند که ویژگی‌های آن ویژگی‌های شیء [$this] و در نتیجه ویژگی‌های کلاس [TaxAdminData] هستند. شایان ذکر است که عملیات инициализация در خطوط ۴۸–۵۱ ممکن است ویژگی‌هایی را به شیء [$this] اضافه کرده باشد. بنابراین، اگر بنویسیم:
    $this->x = "1000";

در این صورت، ویژگی [x] به شیء [$this] اضافه می‌شود، هرچند این ویژگی در کلاس [TaxAdminData] تعریف نشده است. آنچه مسلم است این است که ویژگی‌های موجود در خطوط ۶ تا ۲۰ واقعاً بخشی از شیء [$this] هستند، اما ممکن است مقداردهی نشده باشند. این یک اشتباه رایج است؛ تنها کافی است یک اشتباه تایپی در نام یک ویژگی در فایل [taxadmindata.json] رخ دهد؛

  • خطوط 54–57: تمام ویژگی‌های [$this] بررسی می‌شوند، و اگر هر یک از آن‌ها مقداردهی نشده باشند، یک استثنا پرتاب می‌شود؛
  • ممکن است یک ویژگی با مقدار نادرست مقداردهی اولیه شود. در PHP، امکان مشخص کردن نوع برای ویژگی‌ها وجود ندارد. بنابراین، عملیات:
$this→plafondQfDemiPart=’abcd’

ممکن است، هرچند که ویژگی [$plafondQfDemiPart] باید یک عدد حقیقی باشد؛

  • خطوط 59–71: یک بررسی انجام می‌شود تا اطمینان حاصل شود که هر ویژگی کلاس دارای یک عدد حقیقی مثبت یا مقدار صفر است. این کار توسط تابع [check] در خط 76 انجام می‌شود. پارامتر آن [$value] یا یک مقدار واحد است یا یک آرایه از مقادیر؛
  • خط ۶۲: تابع [check] یک شیء از نوع [\stdClass] را با دو ویژگی بازمی‌گرداند:
    • [erreur]: در صورت رخ دادن خطا برابر TRUE و در غیر این صورت برابر FALSE تنظیم می‌شود؛
    • [value]: مقدار عددی واقعی متناظر با پارامتر [$value] که به عنوان پارامتر ارسال شده است، خط ۶۲؛
  • خط ۶۴: بررسی می‌کنیم که اعتبارسنجی موفق بوده یا خیر؛
  • خط ۶۶: اگر یک ویژگی عدد حقیقی مثبت یا صفر نباشد، یک استثنا پرتاب می‌شود؛
  • خط ۶۹: در غیر این صورت، مقدار عددی آن را ثبت کنید؛
  • خط ۷۳: شیء [$this] به عنوان نتیجه بازگردانده می‌شود؛

تابع [check] به صورت زیر است:


private function check($value): \stdClass {
    // $value یا یک آرایه از عناصر است یا یک عنصر واحد
    // یک آرایه ایجاد می‌شود
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // ما آرایهٔ عناصر با نوع نامعلوم را به آرایه‌ای از اعداد حقیقی تبدیل می‌کنیم
    $newTableau = [];
    $result = new \stdClass();
    //عناصر آرایه باید اعداد اعشاری مثبت یا صفر باشند
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        //عدد اعشاری در newTableau قرار می‌گیرد
        $newTableau[] = (float) $tableau[$i];
      } else {
        //خطا ثبت شده است
        $result->erreur = TRUE;
        //خروج
        return $result;
      }
    }
    // نتیجه را بازگردانید
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      //یک مقدار واحد
      $result->value = $newTableau[0];
    } else {
      // یک لیست از مقادیر
      $result->value = $newTableau;
    }
    return $result;
  }

توضیحات

  • خط ۱: پارامتر [$value] یا یک آرایه است یا یک عنصر واحد. علاوه بر این، نوع آن نامشخص است. مقدار از فایل [taxadmindata.json] می‌آید. بسته به مقادیر ثبت‌شده در این فایل، مقادیر خوانده‌شده ممکن است عدد صحیح، عدد اعشاری، رشته‌ای یا بولین باشند. برای مثال:

"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",

در حالت ۱، مقدار از نوع [entier] است؛ در حالت ۲، از نوع [réel] است؛ در حالت ۳، مقدار از نوع [string] است که می‌تواند به عدد تبدیل شود؛ در حالت ۴، مقدار از نوع [string] است که نمی‌تواند به عدد تبدیل شود؛

  • خطوط ۴–۸: یک آرایه از پارامتر [$value] که به‌عنوان پارامتر در خط ۱ دریافت شده، ایجاد می‌شود؛
  • خط ۱۰: آرایه باید با اعداد حقیقی پر شود؛
  • خط ۱۱: نتیجه یک شیء از نوع [\stdClass] خواهد بود؛
  • خط ۱۳: عبارت رابطه‌ای برای یک عدد حقیقی مثبت یا صفر؛
  • خطوط ۱۴–۲۴: ما بررسی می‌کنیم که همه عناصر آرایه [$tableau] اعداد حقیقی مثبت یا صفر هستند، و آرایه [$newTableau] را با این عناصر که به نوع [float] تبدیل شده‌اند، پر می‌کنیم (خط ۱۷);
  • خطوط ۱۸–۲۳: به محض اینکه عنصری شناسایی شود که عدد حقیقی مثبت یا صفر نباشد، خطا در نتیجه ثبت می‌شود و این نتیجه بازگردانده می‌شود؛
  • خطوط ۲۵–۳۴: حالتی که تمام عناصر آرایه [$tableau] صحیح اعلام شده‌اند؛
  • خط ۳۲: مقدار بازگشتی [$result→value] یا یک آرایه از اعداد حقیقی [float] است یا یک عدد حقیقی واحد؛

تابع [__toString] در خطوط 82–85 رشته jSON را که شامل ویژگی‌ها و مقادیر شیء [$this] است، بازمی‌گرداند.

خطوط ۸۷–۱۱۰: گترها و سترها (getters و setters) کلاس؛

توجه: گاهی ممکن است نوشتن تمام متدهای get/set برای یک کلاس کمی خسته‌کننده باشد، به‌ویژه وقتی ویژگی‌های زیادی وجود دارد. NetBeans می‌تواند این متدها و همچنین سازنده (constructor) را به‌طور خودکار تولید کند. برای این کار، کافی است ویژگی‌های [1] را انتخاب کنید:

Image

  • در [2]، روی مکان مورد نظر برای درج کد کلیک راست کرده و سپس گزینه [Insert Code] را انتخاب کنید؛

Image

  • برای [4]، مشخص کنید که می‌خواهید کانستراکتور را تولید کنید؛
  • در [5]، تمام ویژگی‌ها را تیک بزنید: این بدان معناست که شما می‌خواهید سازنده برای هر یک از ویژگی‌ها یک پارامتر داشته باشد؛
  • در [6]، از سبک سازنده‌های جاوا استفاده کنید؛
  • در [7]، مشخص کنید که صراحتاً می‌خواهید کلمه کلیدی [public] قبل از سازنده قرار گیرد؛
  • در [8]، ذخیره کنید؛

Image

  • در [9]، NetBeans کانتراکتور را تولید کرده است. با این حال، نتوانسته است نوع پارامترها را مشخص کند زیرا آن‌ها را نمی‌شناسد. خودتان آن‌ها را اضافه کنید: [10];

برای تولید گترها و سترها، مراحل ۲ تا ۴ را تکرار کنید و در مرحلهٔ ۴، [Getter and Setter] را انتخاب کنید:

Image

  • برای [5]، مشخص کنید که برای هر ویژگی، گیرنده و تنظیم‌کننده می‌خواهید؛
  • در [6]، مشخص کنید که می‌خواهید گترها و سترها به سبکی که در جاوا استفاده می‌شود باشند: setAttribut, getAttribut;
  • در [7]، مشخص کنید که می‌خواهید این گترها و سترها عمومی باشند؛
  • در [8] تأیید کنید؛

Image

  • در [9]، گترها و سترهای تولید شده توسط NetBeans;

این گترها و سترها را حذف کرده و مراحل ۲ تا ۷ را تکرار کنید.

  • در [8]، گزینه [Fluent Setter] را که قبلاً تیک نزده بودیم، تیک بزنید؛

نتیجه به شرح زیر است:

Image

هر setter با یک عملیات [return $this] پایان می‌یابد. این امکان را می‌دهد که ویژگی‌ها به صورت زیر مقداردهی اولیه شوند:

$data→setLimites($limites)→setCoeffR($coeffR)→setCoeffN($coeffN) ;

در واقع، مقدار [$data→setLimites($limites)] (خط ۳۲ کد) برابر با [$this] است، بنابراین در اینجا [$data] است. بنابراین می‌توانیم متد [setCoeffR($coeffR)] را روی این شیء فراخوانی کنیم، و به همین ترتیب، زیرا این متد نیز به نوبه خود، [$this] را برمی‌گرداند (خط ۳۷ کد). این سبک نوشتن متدهای کلاس—جایی که متدهایی که نباید چیزی بازگردانند، شیء [$this] را بازمی‌گردانند—به نام نگارش روان (fluent writing) شناخته می‌شود. این سبک استفاده از این متدها را آسان‌تر می‌کند.

8.4. رابط [InterfaceImpots]

اکنون رابط زیر را تعریف می‌کنیم: [InterfaceImpots]: [InterfaceImpots.php]:


<?php

// فضای نام
namespace Application;

interface InterfaceImpots {

  // بازیابی داده‌ها در مورد سطوح مالیاتی برای امکان محاسبه مالیات
  //ممکن است استثناء ExceptionImpots را ایجاد کند
  public function getTaxAdminData(): TaxAdminData;

  //رابط می‌تواند مالیات را محاسبه کند
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  //رابط می‌تواند داده‌ها را از فایل‌های متنی پردازش کند
  //$usersFilename: فایل داده‌های کاربر شامل وضعیت تأهل، تعداد فرزندان و حقوق سالانه
  //$resultsFilename: فایل نتایج حاوی وضعیت تأهل، تعداد فرزندان، حقوق سالانه و مبلغ مالیات
  //$errorsFilename: فایلی حاوی خطاهای رخ‌داده
  //ممکن است استثناء ExceptionImpots را ایجاد کند
  public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}

نظرات

  • خط ۴: این رابط در فضای نام [Application] قرار می‌گیرد؛
  • خط ۶: رابط برای محاسبه مالیات‌ها؛
  • خط ۱۰: متد [getTaxAdminData] برای بازیابی داده‌ها از مراجع مالیاتی در یک شیء از نوع [TaxAdminData] که همین حالا توصیف کردیم، استفاده خواهد شد. از آنجایی که این داده‌ها ممکن است در یک فایل، پایگاه داده یا حتی در شبکه ذخیره شده باشند، متد [getTaxAdminData] ممکن است در بازیابی داده‌ها ناموفق باشد. در این صورت، یک استثنا از نوع [ExceptionImpots] پرتاب خواهد کرد. این روش استاندارد در برنامه‌نویسی شیءگرا برای اعلام خطایی است که در یک متد یا سازنده (constructor) رخ داده است؛
  • خط ۱۳: متد [calculerImpot] برای محاسبه مالیات یک کاربر استفاده خواهد شد؛
  • خط ۲۰: متد [executeBatchImpots] برای محاسبه مالیات چندین مودی استفاده خواهد شد؛
    • [$usersFileName] نام فایل متنی حاوی داده‌های مالیات‌دهندگان است؛
    • [$resultsFileName] نام فایل متنی حاوی مبالغ مالیات این مودیان است؛
    • [$errorsFileName] نام فایل متنی حاوی خطاهای رخ‌داده در حین پردازش این فایل‌ها است؛

محتویات فایل متنی [$usersFileName] ممکن است به شرح زیر باشد:


oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

توجه داشته باشید که خطوط ۵ و ۷ حاوی ورودی‌های نادرست هستند.

محتویات فایل متنی [$resultsFileName] به شرح زیر خواهد بود:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}

و محتوای فایل متنی [$errorsFileName] به شرح زیر خواهد بود:

la ligne 5 du fichier taxpayersdata.txt est erronée
la ligne 7 du fichier taxpayersdata.txt est erronée

8.5. کلاس [Utilitaires]

ما همچنین کلاس [Utilitaires] را در فایلی به نام [Utilitaires.php] تعریف می‌کنیم:


<?php

// فضای نام
namespace Application;

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

  public static function cutNewLinechar(string $ligne): string {
    // علامت پایان خط برای $ligne در صورت وجود حذف می‌شود
    $longueur = strlen($ligne);  // طول خط
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // end – خط را بازمی‌گرداند
    return($ligne);
  }
}

توضیحات

  • خط ۴: کلاس [Utilitaires] همچنین در فضای نام [Exemples] قرار داده می‌شود؛
  • خط 9: متد [cutNewLinechar] هر کاراکتر پایان خط را از متنی که به عنوان پارامتر به آن داده می‌شود، حذف می‌کند. این متد خط جدید حاصل را برمی‌گرداند. توجه داشته باشید که این یک متد استاتیک است، به این معنی که به شکل [Utilitaires::cutNewLineChar] فراخوانی خواهد شد؛

8.6. کلاس انتزاعی [AbstractBaseImpots]

رابط [InterfaceImpots] توسط کلاس انتزاعی زیر [AbstractBaseImpots] پیاده‌سازی خواهد شد: [AbstractBaseImpots.php]:


<?php

// فضای نام
namespace Application;

// تعریف یک کلاس انتزاعی AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
  // داده‌های مرجع مالیاتی
  private $taxAdminData = NULL;

  //داده‌های مورد نیاز برای محاسبه مالیات
  abstract function getTaxAdminData(): TaxAdminData;

//محاسبه مالیات
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié: بله، نه
    //$enfants: تعداد فرزندان
    // $salaire: حقوق سالانه
    // $this->taxAdminData: داده‌های سازمان مالیاتی
    //
    // ما بررسی می‌کنیم که داده‌های سازمان مالیاتی را داریم
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    //محاسبه مالیات با فرزندان
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    //محاسبه مالیات به استثنای فرزندان
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      //اعمال سقف ضریب خانوادگی
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART یورو برای دو فرزند اول
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        //$PLAFOND_QF_DEMI_PART یورو برای دو فرزند اول، دو برابر آن برای فرزندان بعدی
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // بالاترین نرخ مالیات اعمال می‌شود
    if ($impot1 > $impot2) {
      $impot = $impot1;
      $taux = $result1["taux"];
      $surcôte = $result1["surcôte"];
    } else {
      $surcôte = $impot2 - $impot1 + $result2["surcôte"];
      $impot = $impot2;
      $taux = $result2["taux"];
    }
    // محاسبه هرگونه تخفیف مالیاتی
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    //محاسبه هرگونه معافیت مالیاتی
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // نتیجه
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

// --------------------------------------------------------------------------
  private function calculerImpot2(string $marié, int $enfants, float $salaire): array {

    // نتیجه
    return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
  }

  //revenuImposable=سال‌تدریس-مزایا
  // مبلغ معافیت حداقل و حداکثری دارد
  private function getRevenuImposable(float $salaire): float {

    // نتیجه
    return floor($revenuImposable);
  }

// هرگونه کاهش را محاسبه می‌کند
  private function getDecôte(string $marié, float $salaire, float $impots): float {

    // نتیجه
    return ceil($décôte);
  }

//هرگونه کاهش را محاسبه می‌کند
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

    // نتیجه
    return ceil($réduction);
  }

  public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {

  }

}

نظرات

  • خط ۴: کلاس [AbstractBaseImpots] در فضای نام [Application] قرار خواهد گرفت، مانند سایر عناصر برنامه‌ای که در حال حاضر نوشته می‌شود؛
  • خط ۷: کلاس [AbstractBaseImpots] رابط [InterfaceImpots] را پیاده‌سازی می‌کند؛
  • خط ۹: داده‌های مرجع مالیاتی در ویژگی [$taxAdminData] ذخیره خواهد شد؛
  • خط ۱۲: پیاده‌سازی متد [getTaxAdminData] این رابط. هنوز نمی‌دانیم چگونه این متد را تعریف کنیم: در پاراگراف قبلی مثالی دیدیم که در آن داده‌های سازمان مالیاتی از یک فایل jSON گرفته شده بود. ما به مورد دیگری نگاه خواهیم کرد که در آن نیاز است داده‌ها از یک پایگاه داده بازیابی شوند. تعریف محتوای متد [getTaxAdminData] بر عهده کلاس‌های مشتق خواهد بود. دو مورد قبلی منجر به ایجاد دو کلاس مشتق خواهند شد. بنابراین متد [getTaxAdminData] به صورت انتزاعی (abstract) اعلام می‌شود، که به طور خودکار خود کلاس را نیز انتزاعی (abstract) می‌کند (خط ۷)؛
  • خطوط ۱۵–۶۴: تابع محاسبه مالیات که پیش از این در بخش‌های link و link با آن مواجه شده‌ایم؛
  • نسخه ۰۲ داده‌های مرجع مالیاتی را در یک آرایه asociative [$taxAdminData] ذخیره می‌کرد. نسخه ۰۳ آن را در ویژگی [$this→taxAdminData] ذخیره می‌کند. اولین تفاوت بین این دو راه‌حل، تفاوت در دیده‌شدن داده‌های مالیاتی است:
    • در نسخه 02، جدول همبست [$taxAdminData] دید جهانی نداشت. بنابراین به عنوان پارامتر به تمام توابع محاسبه مالیات ارسال می‌شد؛
    • در نسخه 03، ویژگی [$this→taxAdminData] برای تمام متدهای کلاس دید جهانی دارد. بنابراین، این ویژگی به عنوان پارامتر به تمام توابع محاسبه مالیات ارسال نمی‌شود؛
  • تفاوت دوم از این واقعیت ناشی می‌شود که نسخه 03 توابع را با متدهای کلاس جایگزین می‌کند. اکنون هر فراخوانی متد با استفاده از یک عبارت [$this→getMéthode(…)] (خطوط 27، 31، 57، 60) انجام می‌شود؛
  • تفاوت سوم این است که وقتی متد [calculerImpot] کار خود را آغاز می‌کند، نمی‌داند که آیا ویژگی [private $taxAdminData] که به آن نیاز دارد، مقداردهی اولیه شده است یا خیر. این به این دلیل است که سازنده کلاس آن را مقداردهی اولیه نمی‌کند. بنابراین این روش [calculerImpot] است که باید این کار را با استفاده از روش [getTaxAdminData] در خط ۱۲ انجام دهد. این کاری است که در خطوط ۲۳–۲۵ انجام می‌شود؛
  • به جز این تفاوت‌ها، روش‌های محاسبه مالیات همانند نسخه‌های قبلی باقی می‌مانند؛

تابع [executeBatchImpots] به شرح زیر است:


public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
    // هنگام کار با فایل‌ها خطاهای زیادی ممکن است رخ دهد
    try {
      //خطاهای باز کردن فایل
      $errors = fopen($errorsFileName, "w");
      if (!$errors) {
        throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
      }
      // باز کردن فایل نتایج
      $results = fopen($resultsFileName, "w");
      if (!$results) {
        throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
      }
      // خواندن داده‌های کاربر
      //هر خط به صورت زیر قالب‌بندی شده است: وضعیت تأهل، تعداد فرزندان، حقوق سالانه
      $data = fopen($usersFileName, "r");
      if (!$data) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
      }
      // ردیف فعلی فایل داده‌های کاربر پردازش می‌شود
      //که در قالب زیر است: وضعیت تأهل، تعداد فرزندان، حقوق سالانه
      $num = 1;         // شماره سطر فعلی
      $nbErreurs = 0;   // تعداد خطاهای رخ‌داده
      while ($ligne = fgets($data, 100)) {
        //اشکال‌زدایی
        //  print "شماره خط " . ($i + 1) . " : " . $ligne;
        // حذف هر کاراکتر پایان خط
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // سه فیلد 'married:children:salary' را که $ligne را تشکیل می‌دهند بازیابی می‌کند
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // آنها را بررسی کنید
        //وضعیت تأهل باید «بله» یا «خیر» باشد
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // تعداد فرزندان باید یک عدد صحیح باشد
          $enfants = trim($enfants);
          if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // حقوق یک عدد صحیح است و شامل سنت یورو نمی‌شود
          $salaire = trim($salaire);
          if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // خطا؟
        if ($erreur) {
          fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
          $nbErreurs++;
        } else {
          //مالیات محاسبه می‌شود
          $result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
          // نتیجه در فایل نتایج نوشته می‌شود
          $result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
          fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
        }
        // خط بعدی
        $num++;
      }
      // آیا خطایی وجود دارد؟
      if ($nbErreurs > 0) {
        throw new ExceptionImpots("Il y a eu des erreurs", 15);
      }
    } catch (ExceptionImpots $ex) {
      // استثنا را مجدداً مطرح کنید
      throw $ex;
    } finally {
      // تمام فایل‌ها را ببندید
      fclose($data);
      fclose($results);
      fclose($errors);
    }
  }

توضیحات کد

  • خط ۱: تابع سه پارامتر می‌گیرد:
    • [$usersFileName]: نام فایل متنی حاوی داده‌های مودیان مالیاتی. هر خط از متن شامل داده‌های یک مودی مالیاتی به فرمت زیر است: وضعیت تأهل (بله/خیر)، تعداد فرزندان، حقوق سالانه:
oui,2,55555
oui,2,50000
  • (ادامه)
    • [$resultsFileName]: نام فایل متنی که نتایج را در خود خواهد داشت. هر خط از متن به فرمت زیر خواهد بود:
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
  • (ادامه)
    • [$errorsFileName]: نام فایل متنی حاوی خطاها:

la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
  • خط ۳: از آنجایی که ممکن است چندین عملیات یک استثنا (exception) ایجاد کنند، یک بلوک try/catch/finally کل کد متد را در بر می‌گیرد؛
  • خطوط ۳–۱۹: سه فایل باز می‌شوند. به محض اینکه یک عملیات باز کردن با خطا مواجه شود، یک استثنا پرتاب می‌شود؛
  • خط ۲۴: خطوط فایل [$data] یکی یکی در بلوک‌هایی با حداکثر ۱۰۰ کاراکتر خوانده می‌شوند (تمام خطوط کمتر از ۱۰۰ کاراکتر طول دارند)؛
  • خط ۲۸: از متد استاتیک [Utilitaires::cutNewLineChar] برای حذف هرگونه کاراکتر پایان خط استفاده می‌شود؛
  • خط ۳۰: سه عنصر خط خوانده‌شده بازیابی می‌شوند؛
  • خطوط ۳۳–۵۲: اعتبار سه عنصر بررسی می‌شود. در اینجا، اگر خطایی رخ دهد، هیچ استثنایی پرتاب نمی‌شود؛ در عوض، پیام خطا در فایل متنی [$errors] (خط ۵۵) نوشته می‌شود؛
  • خط ۵۹: اگر خط خوانده شده معتبر باشد، مالیات محاسبه می‌شود. نتیجه به صورت یک آرایهٔ asociative ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux] بازگردانده می‌شود؛
  • خط ۶۱: کلیدهای [marié, enfants, salaire] به نتیجهٔ به‌دست‌آمده اضافه می‌شوند؛
  • خط ۶۱: نتیجه در قالب رشته jSON که نمایانگر نتیجه به‌دست‌آمده است، در فایل متنی [$results] نوشته می‌شود؛
  • خطوط ۶۸–۷۰: پس از پردازش فایل [$data]، تعداد خطوط خطا بررسی می‌شود. اگر حتی یک خط خطا وجود داشته باشد، یک استثنا پرتاب می‌شود؛
  • خطوط ۷۱–۷۴: استثنایی که ممکن است توسط کد پرتاب شده باشد، گرفته شده و بلافاصله دوباره پرتاب می‌شود (خط ۷۳). هدف از این تکنیک این است که تضمین کند یک عبارت [finally] در خطوط 74–79 وجود دارد: صرف‌نظر از اینکه اجرای کد متد چگونه پایان یابد، سه فایلی که ممکن است توسط این کد باز شده باشند، بسته می‌شوند. بستن فایلی که باز نشده است خطا ایجاد نمی‌کند؛

8.7. کلاس [ImpotsWithTaxAdminDataInJsonFile]

کلاس انتزاعی [AbstractBaseImpots] متد [getTaxAdminData] از رابط [InterfaceImpots] را پیاده‌سازی نمی‌کند. بنابراین باید آن را در یک کلاس مشتق تعریف کنیم. ما این کار را در کلاس مشتق زیر، [ImpotsWithTaxAdminDataInJsonFile]، انجام می‌دهیم:


<?php

// فضای نام
namespace Application;

// تعریف کلاس ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
  // یک ویژگی از نوع Data
  private $taxAdminData;

  // سازنده
  public function __construct(string $jsonFileName) {
    // $this->taxAdminData از فایل jSON مقداردهی اولیه می‌شود
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
  }

  // داده‌های مورد نیاز برای محاسبه مالیات را بازمی‌گرداند
  public function getTaxAdminData(): TaxAdminData {
    // ویژگی [$this->taxAdminData] را بازمی‌گرداند
    return $this->taxAdminData;
  }

}

توضیحات

  • خط ۷: کلاس [ImpotsWithTaxAdminDataInJsonFile] از کلاس انتزاعی [AbstractBaseImpots] ارث می‌برد. این کلاس باید متد [getTaxAdminData] را تعریف کند، که کلاس والدین آن را تعریف نکرده است؛
  • خط ۹: ویژگی [$taxAdminData] حاوی داده‌های مقامات مالیاتی خواهد بود؛
  • خطوط ۱۲–۱۵: سازنده تنها پارامتر آن نام فایل `jSON` حاوی داده‌های مالیاتی است؛
  • خط ۱۴: یک شیء از نوع [TaxAdminData] ایجاد و سپس مقداردهی اولیه می‌شود. این عملیات ممکن است یک استثنا از نوع [ExceptionImpots] را پرتاب کند. این استثنا تا اسکریپت اصلی [main.php] propagate خواهد شد؛
  • خطوط ۱۸–۲۰: یک بدنه برای متد [getTaxAdminData] که کلاس والد آن را تعریف نکرده بود، ارائه شده است. در اینجا، کافی است اطمینان حاصل شود که ویژگی [$this->taxAdminData] توسط سازنده مقداردهی اولیه می‌شود؛

8.8. اسکریپت [main.php]

این کلاس‌ها و رابط‌ها توسط اسکریپت زیر، [main.php استفاده می‌شوند:


<?php

// پابندی سخت‌گیرانه به انواع اعلام‌شدهٔ پارامترهای تابع
declare(strict_types = 1);

//فضای نام
namespace Application;

// شامل کردن رابط‌ها و کلاس‌ها
require_once __DIR__ . '/InterfaceImpots.php';
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . '/ExceptionImpots.php';
require_once __DIR__ . '/Utilitaires.php';
require_once __DIR__ . '/AbstractBaseImpots.php';
require_once __DIR__ . "/ImpotsWithTaxAdminDataInJsonFile.php";

// test -----------------------------------------------------
// تعریف ثابت‌ها
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // ایجاد یک شیء ImpotsWithTaxAdminDataInJsonFile
  $impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // اجرای دسته‌ای مالیات
  $impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // نمایش خطا
  print $ex->getMessage() . "\n";
}
// پایان
print "Terminé\n";
exit();


توضیحات

  • خط ۴: پیروی سخت‌گیرانه از انواع پارامترهای تابع اعمال می‌شود؛
  • خط ۷: اسکریپت [main.php] نیز در فضای نام [Application] قرار داده شده است؛
  • خطوط ۱۰–۱۵: به مفسر PHP گفته می‌شود که کلاس‌ها و رابط‌های مورد استفادهٔ اسکریپت در کجا قرار دارند. توجه کنید که در اینجا از دستور use برای اعلام نام کامل کلاس‌های مورد استفاده در اسکریپت استفاده نکرده‌ایم. این کار غیرضروری است زیرا اسکریپت و کلاس‌ها در یک فضای نام یکسان، [Application]، قرار دارند؛
  • خطوط ۱۸–۲۲: نام فایل‌های متنی مورد استفاده در اسکریپت؛
  • خطوط ۲۴–۲۹: یک شیء [ImpotsWithTaxAdminDataInJsonFile] ایجاد می‌شود و هرگونه خطا (exception) مدیریت می‌شود؛
  • خط ۲۸: متد [executeBatchImpots] اجرا می‌شود که مالیات را برای تمام مودیان در فایل [TAXPAYERSDATA_FILENAME] محاسبه می‌کند. نتایج در فایل [RESULTS_FILENAME] و هرگونه خطا در فایل [ERRORS_FILENAME] نوشته خواهد شد؛
  • خطوط ۲۹–۳۲: در صورت وقوع خطای مرگبار، پیام خطا نمایش داده می‌شود؛

نتایج

با فایل مودی زیر [taxpayersdata.txt]:


oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

فایل خطای زیر [errors.txt] تولید می‌شود:


la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée

و فایل نتایج زیر [resultats.txt]:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}