Skip to content

11. تمرين عملي – الإصدار 4

سيطبق تطبيق حساب الضرائب البنية الطبقية التالية:

Image

سنعيد استخدام العناصر من الإصدار 3 من القسم المرتبط، مع تعديلها لتتناسب مع البنية الجديدة للتطبيق. يُطلق على هذا أحيانًا اسم "إعادة الهيكلة". هنا، نفترض أن البيانات المطلوبة للتطبيق مخزنة في ملفات نصية. ستتولى طبقة [Dao] التعامل مع هذه الملفات.

11.1. شجرة البرامج النصية

Image

11.2. الكائنات المتبادلة بين الطبقات

سنحتفظ ببعض الكائنات من الإصدار 3. ندرجها هنا للتذكير.

الاستثناء [ExceptionImpots] هو الاستثناء الذي ستقوم طبقة [Dao] بإلقائه عندما تواجه مشكلة إما في الوصول إلى البيانات أو في طبيعة البيانات (بيانات غير صحيحة).


<?php
 
// namespace
namespace Application;
 
class ExceptionImpots extends \RuntimeException {
 
  public function __construct(string $message, int $code=0) {
    parent::__construct($message, $code);
  }
}
 

تحتوي فئة [Utilities] على طرق مفيدة لإدارة الملفات النصية (في هذه الحالة، طريقة واحدة):


<?php
 
// namespace
namespace Application;
 
// a class of utility functions
abstract class Utilitaires {
 
  public static function cutNewLinechar(string $ligne): string {
    // delete the end-of-line mark from $ligne if it exists
    $longueur = strlen($ligne);  // line length
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // end - return the line
    return($ligne);
  }
}
 

فئة [TaxAdminData] هي الفئة التي تغلف بيانات إدارة الضرائب:


<?php
 
namespace Application;
 
class TaxAdminData {
  // tax brackets
  private $limites;
  private $coeffR;
  private $coeffN;
  // tax calculation constants
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;
 
  // initialization
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // retrieve the contents of the tax data file
    $fileContents = \file_get_contents($taxAdminDataFilename);

    // we return the object
    return $this;
  }
 
  private function check($value): \stdClass {

    return $result;
  }
 
    // toString
  public function __toString() {
    // object's Json string
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }
 
  // getters and setters
  public function getLimites() {
    return $this->limites;
  }
 

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

}
 

نضيف فئة جديدة [TaxPayerData] تغلف البيانات المكتوبة في ملف النتائج:


<?php
 
// namespace
namespace Application;
 
// data class
class TaxPayerData {
  // data required to calculate the taxpayer's tax liability
  private $marié;
  private $enfants;
  private $salaire;
  // tax calculation results
  private $montant;
  private $surcôte;
  private $décôte;
  private $réduction;
  private $taux;
 
  // setter
  public function setFromParameters(string $marié, int $nbEnfants, int $salaireAnnuel) : TaxPayerData{
    // taxpayer data required for tax calculation
    $this->marié = $marié;
    $this->enfants = $nbEnfants;
    $this->salaire = $salaireAnnuel;
    // initialize the object
    return $this;
  }
 
  // getters and setters
  public function getMarié() {
    return $this->marié;
  }
 

 
  public function setMarié($marié) {
    $this->marié = $marié;
    return $this;
  }
 

 
    // toString
  public function __toString() {
    // object's Json string
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }
 
}
 

ملاحظة: استخدم إنشاء الكود التلقائي لإنشاء المنشئ ووظائف الحصول والتعيين (انظر القسم المرتبط). لاحظ أن وظائف التعيين "سلسة".

11.3. طبقة [DAO]

نركز هنا على الطبقة [1] من تطبيقنا:

Image

11.3.1. واجهة [InterfaceDao]

ستكون واجهة طبقة [DAO] كما يلي [InterfaceDao.php]:


<?php
 
// namespace
namespace Application;
 
interface InterfaceDao {
 
  // reading taxpayer data
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
 
  // reading tax data (tax brackets)
  public function getTaxAdminData(): TaxAdminData;
 
  // recording results
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
 

تعليقات

  • المتطلبات هي كما يلي:
    • يتم تخزين بيانات دافع الضرائب في ملف نصي؛
    • يتم حفظ نتائج حساب الضريبة في ملف نصي؛
    • يتم حفظ أي أخطاء في ملف نصي؛
    • من غير المعروف ما هو التنسيق الذي تتوفر به بيانات مصلحة الضرائب. لكل تنسيق جديد، يجب تنفيذ واجهة [InterfaceDao] بواسطة فئة جديدة؛
    • يجب أن ترمي طرق الواجهة التي تواجه خطأً فادحًا عند الوصول إلى البيانات استثناءً من النوع [TaxException]؛
  • السطر 9: الطريقة التي تسترد بيانات دافع الضرائب [الحالة الاجتماعية، عدد الأبناء، الراتب السنوي]؛
    • المعلمة الأولى هي اسم الملف النصي الذي يحتوي على هذه البيانات؛
    • المعلمة الثانية هي اسم الملف النصي الذي يتم فيه تسجيل أي أخطاء يتم مواجهتها؛
  • السطر 12: الطريقة التي تسترد البيانات من مصلحة الضرائب. لا يتم تمرير أي معلمات هنا لأننا لا نعرف كيف يتم تخزين البيانات؛
  • السطر 15: الطريقة المستخدمة لحفظ نتائج حساب الضريبة في ملف نصي، يتم تمرير اسمه كمعلمة؛

عند كتابة واجهة [InterfaceDao]، نعلم أنه ستكون هناك طرق مختلفة لتنفيذ طريقة [getTaxAdminData] اعتمادًا على كيفية تخزين بيانات إدارة الضرائب. وبالتالي، سيتم تنفيذ واجهة [InterfaceDao] بواسطة فئات مختلفة، تتعامل كل منها مع طريقة تخزين محددة لهذه البيانات (مصفوفات، ملفات نصية، قواعد بيانات، خدمات ويب). ومع ذلك، ستشترك هذه الفئات المشتقة في كود مشترك، وتحديدًا تنفيذ طريقتي [getTaxPayersData] و[saveResults]. ونعلم أن حالة الاستخدام هذه يمكن تنفيذها بطريقتين (انظر الفقرة المرتبطة):

  1. نقوم بإنشاء فئة مجردة C تحتوي على الكود المشترك بين الفئات المشتقة. تنفذ الفئة C الواجهة I، ولكن بعض الطرق التي يجب إعلانها في الفئات المشتقة يتم إعلانها على أنها مجردة في الفئة C، وبالتالي فإن الفئة C نفسها مجردة. ثم نقوم بإنشاء الفئتين C1 و C2 المشتقتين من C، حيث تنفذ كل منهما الطرق غير المحددة (المجردة) لفئتها الأم C بطريقتها الخاصة؛
  2. ننشئ سمة T تكاد تكون مطابقة للفئة المجردة C من الحل السابق. لا تنفذ هذه السمة الواجهة I لأنه، من الناحية النحوية، لا يمكنها ذلك. ثم ننشئ الفئتين C1 و C2 اللتين تنفذان الواجهة I وتستخدمان السمة T. كل ما يتبقى لهذه الفئات هو تنفيذ أساليب الواجهة I التي لا تنفذها السمة T التي تستوردانها؛

في هذا المثال، سنستخدم السمة [TraitDao] هنا.

11.3.2. السمة [TraitDao]

فيما يلي كود السمة [TraitDao] [TraitDao.php]:


<?php
 
// namespace
namespace Application;
 
trait TraitDao {
 
  // reading taxpayer data
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // taxpayer data table
    $taxPayersData = [];
    // error table
    $errors = [];
    // many errors can occur when managing files
    try {
      // reading user data
      // each line has the form marital status, number of children, annual salary
      $taxPayersFile = fopen($taxPayersFilename, "r");
      if (!$taxPayersFile) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$taxPayersFilename]", 12);
      }
      // the current line of the user data file is used
      // in the form of marital status, number of children, annual salary
      $num = 1;         // n° current line
      $nbErreurs = 0;   // number of errors encountered
      while ($ligne = fgets($taxPayersFile, 100)) {
        // empty lines are neglected
        $ligne = trim($ligne);
        if (strlen($ligne) == 0) {
          // next line
          $num++;
          // we loop again
          continue;
        }
        // remove any end-of-line marker
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // we retrieve the 3 fields married:children:salary which form $ligne
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // we check them
        // marital status must be yes or no
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // the number of children must be an integer
          $enfants = trim($enfants);
          if (!preg_match("/^\d+$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // the salary is a whole number without the euro cents
          $salaire = trim($salaire);
          if (!preg_match("/^\d+$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // mistake?
        if ($erreur) {
          $errors[] = "la ligne [$num] du fichier [$taxPayersFilename] est erronée";
          $nbErreurs++;
        } else {
          // memorize information
          $taxPayersData[] = (new TaxPayerData())->setFromParameters($marié, $enfants, $salaire);
        }
        // next line
        $num++;
      }
      // are we at the end of the file?
      if (!feof($taxPayersFile)) {
        // we're out of the loop on a read error
        throw new ExceptionImpots("Erreur lors de la lecture de la ligne n° [$num] du fichier [$taxPayersFilename]");
      } else {
        // we're out of the loop at the end-of-file mark
        // save errors in a text file
        $this->saveString($errorsFilename, implode("\n", $errors));
        // function result
        return $taxPayersData;
      }
    } finally {
      // close the file if it is open
      if ($taxPayersFile) {
        fclose($taxPayersFile);
      }
    }
  }
 
  // recording results
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // save table [$taxPayersData] in text file [$resultsFileName]
    // if text file [$resultsFileName] does not exist, it is created
    $this->saveString($resultsFilename, implode("\n", $taxPayersData));
  }
 
  // saving table results in a text file
  private function saveString(string $fileName, string $data): void {
    // save table [$data] in text file [$fileName]
    // if text file [$fileName] does not exist, it is created
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }
 
}
 

تعليقات

  • السطر 6: هنا نُعرّف سمة، وليس فئة؛
  • الأسطر 9–89: تنفذ الطريقة [getTaxPayersData] الطريقة التي تحمل الاسم نفسه من واجهة [InterfaceDao]. وهي تسترد بيانات دافعي الضرائب [الحالة الاجتماعية، عدد الأبناء، الراتب السنوي ] من ملف نصي باسم [$taxPayersFilename]. وهي تُرجع هذه البيانات كمصفوفة [$taxPayersData] من عناصر من النوع [TaxPayerData] (السطران 67 و81)؛
  • تشبه طريقة [getTaxPayersData] إلى حد كبير طريقة [AbstractBaseImpots::executeBatchImpots] الموضحة في القسم المرتبط، مع الاختلافات التالية:
    • تسترد طريقة [getTaxPayersData] بيانات دافعي الضرائب فقط. ولا تقوم بأي حسابات ضريبية. وهنا، يكون ذلك دور طبقة [business]؛
    • تمامًا مثل طريقة [executeBatchImpots]، تقوم هذه الطريقة بالإبلاغ عن الأخطاء. هنا، يتم تخزين الأخطاء أولاً في مصفوفة [$errors] (السطر 13)، والتي يتم حفظها بعد ذلك في ملف نصي في نهاية العملية (السطر 79). اعتمادًا على الموقف، قد تكون هذه المصفوفة فارغة أو غير فارغة؛
    • في حالة حدوث خطأ فادح، يتم إلقاء استثناء [ExceptionImpots] (السطران 20 و75)؛
  • السطر 73: لاحظ المعالجة التي تتم عند الخروج من الحلقة في الأسطر 26–71. في الواقع، تعاني الدالة [fgets] من عيب يتمثل في إرجاع القيمة المنطقية FALSE سواء عند قراءة الأسطر التي تصادف علامة نهاية الملف أو عند فشل القراءة بسبب خطأ. للتمييز بين الحالتين، نتحقق مما إذا كنا قد وصلنا إلى نهاية الملف باستخدام الدالة [feof]. إذا لم نصل إلى نهاية الملف، فهذا يعني حدوث خطأ، وعندها نلقي باستثناء؛
  • الأسطر 83-88: يتم تنفيذ كتلة [finally] بغض النظر عما إذا كان قد حدث استثناء أثناء معالجة الملف؛
  • السطر 85: إذا تم فتح الملف، فإن "مقبض" الملف [$taxPayersFile] يكون له القيمة المنطقية TRUE؛ وإلا، يكون FALSE؛
  • الأسطر 99–105: الطريقة الخاصة [saveString] المستخدمة في السطر 79 لحفظ مصفوفة الأخطاء في ملف نصي؛
  • السطر 99: تأخذ الطريقة [saveString] معلمتين:
    • [string $filenameوهو اسم الملف النصي المستخدم لحفظ البيانات؛
    • [string $data]، وهي السلسلة التي سيتم حفظها في الملف النصي. ستكون هذه السلسلة عبارة عن تسلسل من الأسطر تنتهي بحرف السطر الجديد \n؛
  • السطر 102: تكتب دالة PHP [file_puts_contents] سلسلة إلى ملف نصي. تفتح الدالة الملف، وتكتب السلسلة فيه، ثم تغلق الملف. وتُرجع القيمة FALSE في حالة حدوث خطأ؛
  • السطر 103: في حالة حدوث خطأ، يتم إلقاء استثناء؛
  • الأسطر 92–96: تنفيذ طريقة [saveResults] الخاصة بواجهة [InterfaceDao]. يتم استخدام الطريقة الخاصة [saveString] مرة أخرى. وهنا، يمثل المعامل الثاني لطريقة [saveString] سلسلة تم تكوينها من المصفوفة [$taxPayersData]، التي تكون عناصرها من النوع [TaxPayerData]. وقد يتساءل المرء عن النتيجة التي ستسفر عنها هذه العملية:

implode("\n", $taxPayersData)

لقد عرّفنا طريقة [__toString] التالية في فئة [TaxPayerData] (انظر القسم المرتبط):


public function __toString() {
    // chaîne Json de l'objet
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

العملية


implode("\n", $taxPayersData)

ستقوم بربط كل عنصر من عناصر المصفوفة [$taxPayersData] — التي تم تحويلها إلى سلسلة باستخدام طريقة [__toString] — بحرف السطر الجديد \n. سيؤدي ذلك إلى سلسلة بالشكل التالي:

json1\njson2\n…

الخلاصة

قامت السمة [TraitDao] بتنفيذ طريقتين من واجهة [InterfaceDao]، وهما [getTaxPayersData] و[saveResults]:


<?php
 
// namespace
namespace Application;
 
interface InterfaceDao {
 
  // reading taxpayer data
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
 
  // reading tax data (tax brackets)
  public function getTaxAdminData(): TaxAdminData;
 
  // recording results
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
 

لا يزال يتعين علينا تنفيذ طريقة [getTaxAdminData]، التي تسترد البيانات من إدارة الضرائب.

11.3.3. فئة [ImpotsWithTaxAdminDataInJsonFile]

تنفذ فئة [ImpotsWithTaxAdminDataInJsonFile] واجهة [InterfaceDao] على النحو التالي:


<?php
 
// namespace
namespace Application;
 
// definition of a ImpotsWithDataInFile class
class DaoImpotsWithTaxAdminDataInJsonFile implements InterfaceDao {
  // use of a line
  use TraitDao;
  // the TaxAdminData object containing tax bracket data
  private $taxAdminData;
 
  // the manufacturer
  public function __construct(string $taxAdminDataFilename) {
    // we want to initialize the [$this->taxAdminData] attribute
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // returns data for tax calculation
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }
}
 

تعليقات

  • السطر 7: الفئة [ImpotsWithTaxAdminDataInJsonFile] تنفذ الواجهة [InterfaceDao]؛
  • السطر 9: تستخدم الفئة [ImpotsWithTaxAdminDataInJsonFile] السمة [traitDao]، والتي، كما نعلم، تنفذ طريقتي [getTaxPayersData] و[saveResults] للواجهة [InterfaceDao]. وبالتالي، كل ما تبقى هو أن تقوم الفئة [ImpotsWithTaxAdminDataInJsonFile] بتنفيذ الطريقة [getTaxAdminData]، التي تسترد البيانات من إدارة الضرائب؛
  • السطر 11: السمة من النوع [TaxAdminData] التي تُرجعها الطريقة [getTaxAdminData] في الأسطر 20-22. يتم تهيئة هذه السمة بواسطة المُنشئ في الأسطر 14-17؛

لقد انتهينا الآن من طبقة [DAO] في تطبيقنا: لدينا فئة تنفذ بالكامل واجهة [InterfaceDao] التي حددناها. يمكننا الآن الانتقال إلى طبقة [business].

11.4. طبقة [business]

سنقوم الآن بتنفيذ الطبقة [2] من بنيتنا:

Image

11.4.1. واجهة [InterfaceMétier]

ستكون واجهة الطبقة [الأعمال] كما يلي:


<?php
 
// namespace
namespace Application;
 
interface InterfaceMetier {
 
  // calculating a taxpayer's taxes
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;
 
  // batch mode tax calculation
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
 

تعليقات

  • السطر 9: يمكن لواجهة [BusinessInterface] حساب مبلغ الضريبة لمكلف فردي شريطة تزويدها بالمعلومات التالية: الحالة الاجتماعية، عدد الأبناء، الراتب السنوي. لا تستخدم طريقة [calculateTax] طبقة [DAO]، لذا فهي لا ترمي استثناءات؛
  • السطر 9: يمكن لواجهة [BusinessInterface] أيضًا حساب مبلغ الضريبة لمجموعة من دافعي الضرائب الذين تم جمع بياناتهم في ملف نصي باسم [$taxPayersFileName]. وتقوم بكتابة النتائج في ملف نصي باسم [$resultsFileName]. يجب أن تتواصل طريقة [executeBatchImpots] مع طبقة [dao]، التي تتولى الوصول إلى نظام الملفات. قد تنتقل الاستثناءات بعد ذلك من طبقة [dao]، والتي لن تلتقطها طريقة [executeBatchImpots]: ستسمح لها بالانتقال إلى البرنامج النصي الرئيسي. يتم تسجيل الأخطاء غير الفادحة في الملف النصي المسمى [$errorsFileName]؛
  • السطر 9: طريقة [calculateTax] هي طريقة [business] بحتة. وهي لا تهتم بمصدر البيانات التي تستخدمها؛
  • السطر 12: ستتفاعل طريقة [executeBatchImpots] مع طبقة [dao] لقراءة البيانات وكتابتها في ملفات نصية. وستستدعي طريقة الأعمال [calculerImpot] بشكل متكرر؛

11.4.2. فئة [Business]

تنفذ فئة [Metier] واجهة [InterfaceMetier] على النحو التالي:


<?php
 
// namespace
namespace Application;
 
class Metier implements InterfaceMetier {
  // dao layer
  private $dao;
  // tax administration data
  private $taxAdminData;
 
  //---------------------------------------------
  // setter couche [dao]
  public function setDao(InterfaceDao $dao) {
    $this->dao = $dao;
    return $this;
  }
 
  public function __construct(InterfaceDao $dao) {
    // a reference is stored on the [dao] layer
    $this->dao = $dao;
    // recover data for tax calculation
    // method [getTaxAdminData] may throw a ExceptionImpots exception
    // we then let it go back to the calling code
    $this->taxAdminData = $this->dao->getTaxAdminData();
  }
 
// tAX CALCULATION
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

    // result
    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 {

    // result
    return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
  }
 
  // revenuImposable=annualwage-discount
  // the allowance has a minimum and a maximum
  private function getRevenuImposable(float $salaire): float {

    // result
    return floor($revenuImposable);
  }
 
// calculates any discount
  private function getDecôte(string $marié, float $salaire, float $impots): float {

    // result
    return ceil($décôte);
  }
 
// calculates any reduction
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

    // result
    return ceil($réduction);
  }
 
  // batch mode tax calculation
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {

    // recording results
    $this->dao->saveResults($resultsFileName, $results);
  }
 
}
 

تعليقات

  • السطر 6: تنفذ فئة [Business] واجهة [BusinessInterface]، أي طريقتي [calculateTax] (الأسطر 30–34) و[executeBatchTaxes] (الأسطر 66–70)؛
  • السطر 8: إشارة إلى طبقة [dao]. وهذا مطلوب حتى تعرف طبقة [business] أين تبحث عندما تحتاج إلى بيانات خارجية. سيتم تهيئة هذه السمة عبر المُعيّن في الأسطر 14–17 أو عبر المُنشئ في الأسطر 19–26؛
  • السطر 10: الكائن من النوع [TaxAdminData] الذي يغلف بيانات إدارة الضرائب. هذه البيانات مطلوبة من قبل طريقة الأعمال [calculateTax]. يتم تهيئة هذه السمة عبر المنشئ في الأسطر 19–26؛
  • الأسطر 19-26: يقوم المنشئ بتهيئة سمتي الفئة:
    • يتم تهيئة السمة [$dao] بالمرجع الذي يتم تمريره كمعلمة إلى المنشئ. لاحظ أن نوع هذه المعلمة هو نوع واجهة [InterfaceDao]، مما يسمح بتهيئة فئة [Metier] بواسطة أي فئة تنفذ هذه الواجهة؛
    • يتم تهيئة السمة [$taxAdminData] عن طريق استدعاء طريقة [getTaxAdminData] الخاصة بطبقة [dao]؛

نستنتج أنه عند تنفيذ الطريقتين [calculateTaxes] و [executeBatchTaxes]، يتم تهيئة كل من السمتين [$dao] و [$taxAdminData].

طريقة [calculateTaxes] هي كما يلي:


public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié : oui, non
    // $enfants : nombre d'enfants
    // $salaire : salaire annuel
    // $this->taxAdminData : données de l'administration fiscale
    //
    // on vérifie qu'on a bien les données de l'administration fiscale
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    // calcul de l'impôt avec enfants
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    // calcul de l'impôt sans les enfants
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      // application du plafonnement du quotient familial
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART euros pour les 2 premiers enfants
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        // $PLAFOND_QF_DEMI_PART euros pour les 2 premiers enfants, le double pour les suivants
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // on prend l'impôt le plus fort
    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"];
    }
    // calcul d'une éventuelle décôte
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    // calcul d'une éventuelle réduction d'impôts
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // résultat
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }
 

تعليقات

  • هذا الكود مأخوذ من الطريقة [AbstractBaseImpots::calculateTax] في الإصدار 3، كما هو موضح في القسم المرتبط. وينطبق الأمر نفسه على الطرق الخاصة [calculateTax2، getDiscount، getReduction، getTaxableIncome]؛

طريقة [Metier::executeBatchImpots] هي كما يلي:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // we let the exceptions coming from the [dao] layer flow upwards
    // retrieve taxpayer data
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // results table
    $results = [];
    // we exploit them
    foreach ($taxPayersData as $taxPayerData) {
      // tax calculation
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // complete [$taxPayerData]
      $taxPayerData->setMontant($result["impôt"]);
      $taxPayerData->setDécôte($result["décôte"]);
      $taxPayerData->setSurCôte($result["surcôte"]);
      $taxPayerData->setTaux($result["taux"]);
      $taxPayerData->setRéduction($result["réduction"]);
      // put the result in the results table
      $results [] = $taxPayerData;
    }
    // recording results
    $this->dao->saveResults($resultsFileName, $results);
  }
 

تعليقات

  • السطر 1: يجب أن تستدعي الطريقة بشكل متكرر الطريقة [calculateTax] لكل دافع ضرائب موجود في الملف النصي المسمى [$taxPayersFileName]. ويجب أن تكتب النتائج في الملف النصي المسمى [$resultsFileName]. يتم تسجيل الأخطاء غير الفادحة التي تمت مواجهتها في الملف النصي المسمى [$errorsFileName]. لا ترمي الطريقة استثناءات بنفسها ولكنها تسمح بتنتقل تلك التي ترميها طبقة [dao]؛
  • السطر 4: يتم طلب بيانات دافعي الضرائب من طبقة [dao]. وهذا يعيد مصفوفة من العناصر من النوع [TaxPayerData]، وهي فئة من السمات [married، numberOfChildren، salary، amount، deduction، reduction، surcharge، rate] (انظر الفقرة المرتبطة). إذا حدث استثناء هنا، وبما أنه لم يتم التقاطه بواسطة كتلة catch، فسوف ينتقل تلقائيًا إلى الكود المستدعي. وهذا يعني أنه في حالة حدوث استثناء، لا يتم تنفيذ السطر 6؛
  • السطر 6: مصفوفة النتائج من النوع [TaxPayerData]؛
  • الأسطر 8-22: يتم حساب الضريبة لكل عنصر في مصفوفة دافعي الضرائب [$taxPayersData]. للقيام بذلك، يتم استدعاء الطريقة الداخلية [calculateTax] (السطر 10)؛
  • الأسطر 15-19: تُستخدم النتيجة لتهيئة سمات [TaxPayerData] التي لم يتم تهيئتها بعد؛
  • السطر 21: تضاف النتيجة إلى مصفوفة النتائج [$results]؛
  • السطر 24: بمجرد حساب الضريبة لجميع دافعي الضرائب، يتم حفظ النتائج في ملف نصي. وتقوم طبقة [dao] بتنفيذ هذه المهمة؛

الخلاصة

بشكل عام، تعد كتابة طبقة [business] أمرًا بسيطًا إلى حد ما لأنها تتفاعل مع طبقة [DAO]، التي تدير الوصول إلى البيانات إلى جانب معالجة الأخطاء المرتبطة بها.

11.5. النص البرمجي الرئيسي

سنقوم الآن بكتابة البرنامج النصي للطبقة [3] من بنية نظامنا:

Image

النص البرمجي الرئيسي هو كما يلي [main.php]:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;

// error handling by PHP
//ini_set("display_errors", "0");
 
// interface and class inclusion
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . "/TaxPayerData.php";
require_once __DIR__ . "/ExceptionImpots.php";
require_once __DIR__ . "/Utilitaires.php";
require_once __DIR__ . "/InterfaceDao.php";
require_once __DIR__ . "/TraitDao.php";
require_once __DIR__ . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once __DIR__ . "/InterfaceMetier.php";
require_once __DIR__ . "/Metier.php";
// test -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
 
try {
  // creation of the [dao] layer
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // creation of the [business] layer
  $métier = new Metier($dao);
  // tax calculation in batch mode
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // error is displayed
  print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit;
 
 

تعليقات

  • السطر 24: اسم ملف بيانات دافعي الضرائب؛
  • السطر 25: اسم ملف النتائج؛
  • السطر 26: اسم ملف الأخطاء؛
  • السطر 27: اسم ملف JSON الذي يحتوي على بيانات سلطة الضرائب؛
  • السطر 31: إنشاء طبقة [dao]؛
  • السطر 33: إنشاء طبقة [business] استنادًا إلى طبقة [dao] هذه؛
  • السطر 35: تنفيذ طريقة [executeBatchImpots] لطبقة [business]؛
  • الأسطر 36-39: رأينا أن طبقة [business] يمكن أن تطلق استثناءات. يتم التقاطها هنا؛

11.6. الاختبارات المرئية

11.6.1. الاختبار رقم 1

باستخدام ملف دافعي الضرائب التالي [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}

11.6.2. اختبار رقم 2

في البرنامج النصي الرئيسي، نخصص اسم ملف غير موجود لملف دافع الضرائب:

const TAXPAYERS_DATA_FILENAME = "taxpayersdata2.txt";

النتائج المعروضة في وحدة التحكم هي كما يلي:


Warning: fopen(taxpayersdata2.txt): failed to open stream: No such file or directory in C:\Data\st-2019\dev\php7\poly\scripts-console\impots\version-04\TraitDao.php on line 18
Impossible d'ouvrir en lecture les déclarations des contribuables [taxpayersdata2.txt]
Terminé
Done.
  • السطر 1: تحذيرات من مترجم PHP؛
  • السطر 2: رسالة خطأ من الاستثناء الذي أطلقته طبقة [dao]؛

من الممكن إخفاء رسائل الخطأ الصادرة عن مترجم PHP:

Image

السطر 21 من الكود أعلاه يوجه النظام بعدم عرض أخطاء PHP. خلال مرحلة التطوير، من الضروري عرضها. في وضع الإنتاج، يجب إخفاؤها.

وتكون نتائج التنفيذ كما يلي:


Impossible d'ouvrir en lecture les déclarations des contribuables [taxpayersdata2.txt]
Terminé

11.7. الاختبارات [Codeception]

الاختبارات المرئية غير كافية على الإطلاق:

  • نحن عادة ما نقتصر على عدد قليل من الاختبارات؛
  • قد لا نولي اهتمامًا كافيًا أثناء هذا الفحص البصري، وقد تفوتنا بعض التفاصيل؛

في عالم التطوير المهني الواقعي، يتم إعداد الاختبارات من قبل أفراد متخصصين يمثل هذا العمل دورهم الأساسي. وهم يسعون جاهدين لجعل الاختبارات شاملة قدر الإمكان. وللقيام بذلك، يستخدمون أطر عمل الاختبارات.

هنا، سنستخدم إطار عمل Codeception [https://codeception.com/] لأنه يمكن دمجه في NetBeans. إنه إطار عمل يتمتع بمجموعة واسعة من القدرات. سنستخدم القليل منها فقط. الفكرة هي أن يكون لدينا طريقة سريعة، بعد كل إصدار جديد من تمرين التطبيق، للتحقق من أنه يعمل. وجود اختبارات ناجحة يمنح المطور الثقة في الكود الذي كتبوه. هذا عامل مهم.

11.7.1. تثبيت إطار عمل [Codeception]

مثل العديد من مكتبات PHP، يتم تثبيت إطار عمل [Codeception] باستخدام [Composer]. لذا نفتح محطة Laragon (انظر الرابط في الفقرة).

أولاً، نحتاج إلى تثبيت إطار عمل اختبار PHPUnit [https://phpunit.de/]. وذلك لأن Codeception يستخدم إطار عمل PHPUnit خلف الكواليس:

Image

بعد ذلك، نقوم بتثبيت إطار عمل Codeception:

Image

هذا كل شيء. الآن دعونا نلقي نظرة على دمج [Codeception] في NetBeans.

11.7.2. دمج [Codeception] في NetBeans

Image

  • في [1-2]، قم بالوصول إلى خصائص المشروع؛
  • في [3-4]، حددنا [Codeception] كأحد أطر عمل الاختبار الخاصة بالمشروع؛

Image

Image

  • في [5-8]، قم بتهيئة إطار عمل [Codeception] للمشروع؛

Image

  • في [9]، تم إنشاء مجلد [tests]، إلى جانب ملف التكوين [codeception.yml] في [10-11]. الملف [11] هو نفسه الملف [10]. قام Codeception ببساطة بإنشاء مجلد [Important Files] لإعطاء الملف [10] تسمية خاصة؛
  • في [12-13]، نعود إلى خصائص المشروع؛

Image

  • في [14-16]، تم تعيين المجلد [tests] [16] كمجلد اختبار للمشروع؛
  • في [16]، يظهر المجلد [tests] بعد ذلك تحت الاسم الجديد [Test Files]. وجود هذا المجلد في مشروع PHP يشير إلى أن المشروع يتضمن إطار عمل لاختبار الوحدات؛
  • سننشئ اختباراتنا في المجلد [unit] [17]؛

11.7.3. اختبارات لطبقة [dao]

Image

  • سنقوم بإنشاء جميع اختباراتنا في المجلد [unit] [1]؛
  • يجب أن تنتهي أسماء فئات اختبار [Codeception] بالكلمة الرئيسية [Test]، وإلا فلن يتم التعرف على الفئات على أنها فئات اختبار؛

ستكون فئات اختبار [Codeception] الخاصة بنا بالصيغة التالية [https://codeception.com/docs/05-UnitTests]:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// loading the test environment

 
class DaoTest extends \Codeception\Test\Unit {
  // test attributes
  private $attribut1;
 
  public function __construct() {
    parent::__construct();
    // test environment initialization

  }
 
  // tests
  public function testTaxAdminData() {
    // tests
    $this->assertEquals($expected, $actual);
    $this->assertEqualsWithDelta($expected, $actual, $delta);
    $this->assertTrue($actual);
    $this->assertFalse($actual);
    $this->assertNull($actual);
    $this->assertEmpty($actual);
    $this→assertSame($expected, $actual);

  }
 
}

تعليقات

  • السطر 7: ستكون فئات الاختبار في نفس مساحة الاسم الخاصة بالتطبيق قيد الاختبار؛
  • السطران 9 و10: هنا توجد عبارات [require] لتحميل الفئات والواجهات قيد الاختبار؛
  • السطر 12: يجب أن ينتهي اسم فئة الاختبار بالكلمة الرئيسية [Test]. يجب أن تمتد هذه الفئة إلى الفئة [\Codeception\Test\Unit]؛
  • الأسطر 16-20: يسمح لنا المنشئ بتهيئة بيئة الاختبار؛
  • السطر 23: يجب أن تبدأ أسماء طرق الاختبار بالكلمة الرئيسية [test]؛
  • الأسطر 25–31: يمكن استخدام طرق اختبار متنوعة؛

ستكون فئة الاختبار [DaoTest] كما يلي:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/TaxAdminData.php";
require_once ROOT . "/TaxPayerData.php";
require_once ROOT . "/ExceptionImpots.php";
require_once ROOT . "/Utilitaires.php";
require_once ROOT . "/InterfaceDao.php";
require_once ROOT . "/TraitDao.php";
require_once ROOT . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once ROOT . "/InterfaceMetier.php";
require_once ROOT . "/Metier.php";
require_once VENDOR. "/autoload.php";;
// test -----------------------------------------------------
// definition of constants
const TAXADMINDATA_FILENAME = "taxadmindata.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }
 
  // tests
  public function testTaxAdminData() {

  }
}
 

تعليقات

لإنشاء الاختبارات الخاصة بإصدار من تمرين التطبيق، سنستخدم بيئة مطابقة لتلك المستخدمة في البرنامج النصي الرئيسي للإصدار. بالنسبة للإصدار 04، هذا هو البرنامج النصي [main.php] التالي:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// error handling by PHP
ini_set("display_errors", "0");
 
// interface and class inclusion
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . "/TaxPayerData.php";
require_once __DIR__ . "/ExceptionImpots.php";
require_once __DIR__ . "/Utilitaires.php";
require_once __DIR__ . "/InterfaceDao.php";
require_once __DIR__ . "/TraitDao.php";
require_once __DIR__ . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once __DIR__ . "/InterfaceMetier.php";
require_once __DIR__ . "/Metier.php";
// test -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
 
try {
  // creation of the [dao] layer
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // creation of the [business] layer
  $métier = new Metier($dao);
  // tax calculation in batch mode
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // error is displayed
  print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit;

لاختبار طبقة [dao]، في فئة الاختبار:

  • نستخدم البيئة من الأسطر 13–27 من [main.php]؛
  • في منشئ فئة الاختبار، نقوم بإنشاء مثيل لطبقة [dao] كما في السطر 31؛
  • نكتب طرق الاختبار؛

سنمضي على هذا النحو مع جميع فئات الاختبار.

لنعد إلى الكود الكامل لفئة الاختبار:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/TaxAdminData.php";
require_once ROOT . "/TaxPayerData.php";
require_once ROOT . "/ExceptionImpots.php";
require_once ROOT . "/Utilitaires.php";
require_once ROOT . "/InterfaceDao.php";
require_once ROOT . "/TraitDao.php";
require_once ROOT . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once ROOT . "/InterfaceMetier.php";
require_once ROOT . "/Metier.php";
require_once VENDOR. "/autoload.php";;
// test -----------------------------------------------------
// definition of constants
const TAXADMINDATA_FILENAME = "taxadmindata.json";
 
class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }
 
  // tests
  public function testTaxAdminData() {
    // calculation constants
    $this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());
    $this->assertEquals(21037, $this->taxAdminData->getPlafondRevenusCelibatairePourReduction());
    $this->assertEquals(42074, $this->taxAdminData->getPlafondRevenusCouplePourReduction());
    $this->assertEquals(3797, $this->taxAdminData->getValeurReducDemiPart());
    $this->assertEquals(1196, $this->taxAdminData->getPlafondDecoteCelibataire());
    $this->assertEquals(1970, $this->taxAdminData->getPlafondDecoteCouple());
    $this->assertEquals(1595, $this->taxAdminData->getPlafondImpotCelibatairePourDecote());
    $this->assertEquals(2627, $this->taxAdminData->getPlafondImpotCouplePourDecote());
    $this->assertEquals(12502, $this->taxAdminData->getAbattementDixPourcentMax());
    $this->assertEquals(437, $this->taxAdminData->getAbattementDixPourcentMin());
    // tax brackets
    $this->assertSame([9964.0, 27519.0, 73779.0, 156244.0, 0.0], $this->taxAdminData->getLimites());
    $this->assertSame([0.0, 0.14, 0.30, 0.41, 0.45], $this->taxAdminData->getCoeffR());
    $this->assertSame([0.0, 1394.96, 5798.0, 13913.69, 20163.45], $this->taxAdminData->getCoeffN());
  }
 
}

تعليقات

  • الأسطر 10–25: تحميل البيئة المطلوبة للاختبار وتعريف الثوابت؛
  • الأسطر 31–36: إنشاء طبقة [dao] (السطر 34)، يليها تهيئة السمة [$taxAdminData] (السطر 29). تحتوي هذه السمة على بيانات إدارة الضرائب؛
  • الأسطر 39–55: طريقة الاختبار الوحيدة. وتتكون من التحقق من أن محتوى السمة [$taxAdminData] يطابق ما هو متوقع؛
  • الأسطر 41–50: فحص ثوابت حساب الضرائب؛
  • الأسطر 52–55: فحوصات على شرائح الضرائب. تتحقق طريقة [assertSame] من أن كيانين في PHP — في هذه الحالة، مصفوفات — متطابقان؛

لتشغيل فئة الاختبار هذه، اتبع الخطوات التالية:

Image

  • في [1-2]، قم بتشغيل الاختبار؛
  • [3]: نافذة نتائج الاختبار؛
  • [4]: فئة الاختبار التي تم تنفيذها؛
  • [5]: النتائج. هنا، اجتازت طريقة الاختبار الوحيدة؛
  • [6]: عندما يفشل الاختبار، أو بشكل أكثر شيوعًا عندما لا يتم تشغيل أي اختبار، تحقق من النافذة [6]. في أغلب الأحيان، تفشل بيئة الاختبار في التحميل، وبالتالي لا يمكن تنفيذ أي اختبار. الأخطاء المعروضة في [6] هي نفسها التي تراها عند تشغيل برنامج نصي PHP قياسي؛

دعونا نلقي نظرة على مثال لاختبار فاشل:

في فئة الاختبار، ندخل خطأً في تعريف ثابت:


// constantes
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04x");

ثم نقوم بتشغيل الاختبار. والنتيجة هي كما يلي:

Image

في النافذة [4]:

Image

11.7.4. اختبارات طبقة [Business]

تتبع فئة الاختبار [MetierTest] نفس قواعد البناء التي تتبعها فئة [DaoTest]، ولكن هناك المزيد من طرق الاختبار:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/TaxAdminData.php";
require_once ROOT . "/TaxPayerData.php";
require_once ROOT . "/ExceptionImpots.php";
require_once ROOT . "/Utilitaires.php";
require_once ROOT . "/InterfaceDao.php";
require_once ROOT . "/TraitDao.php";
require_once ROOT . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once ROOT . "/InterfaceMetier.php";
require_once ROOT . "/Metier.php";
require_once VENDOR. "/autoload.php";;
// test -----------------------------------------------------
// definition of constants
const TAXADMINDATA_FILENAME = "taxadmindata.json";
 
class MetierTest extends \Codeception\Test\Unit {
  // business layer
  private $métier;
 
  public function __construct() {
    parent::__construct();
    // creation of the [dao] layer
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    // creation of the [business] layer
    $this->métier = new Metier($dao);
  }
 
  // tests
  public function test1() {
    $result = $this->métier->calculerImpot("oui", 2, 55555);
    $this->assertEqualsWithDelta(2815, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
 
  public function test2() {
    $result = $this->métier->calculerImpot("oui", 2, 50000);
    $this->assertEqualsWithDelta(1385, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(384, $result["décôte"], 1);
    $this->assertEqualsWithDelta(347, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
 
  public function test3() {
    $result = $this->métier->calculerImpot("oui", 3, 50000);
    $this->assertEqualsWithDelta(0, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(720, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
 
  public function test4() {
    $result = $this->métier->calculerImpot("non", 2, 100000);
    $this->assertEqualsWithDelta(19884, $result["impôt"], 1);
    $this->assertEqualsWithDelta(4480, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }
 
  public function test5() {
    $result = $this->métier->calculerImpot("non", 3, 100000);
    $this->assertEqualsWithDelta(16782, $result["impôt"], 1);
    $this->assertEqualsWithDelta(7176, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }
 
  public function test6() {
    $result = $this->métier->calculerImpot("oui", 3, 100000);
    $this->assertEqualsWithDelta(9200, $result["impôt"], 1);
    $this->assertEqualsWithDelta(2180, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.3, $result["taux"]);
  }
 
  public function test7() {
    $result = $this->métier->calculerImpot("oui", 5, 100000);
    $this->assertEqualsWithDelta(4230, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
 
  public function test8() {
    $result = $this->métier->calculerImpot("non", 0, 100000);
    $this->assertEqualsWithDelta(22986, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }
 
  public function test9() {
    $result = $this->métier->calculerImpot("oui", 2, 30000);
    $this->assertEqualsWithDelta(0, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0, $result["taux"]);
  }
 
  public function test10() {
    $result = $this->métier->calculerImpot("non", 0, 200000);
    $this->assertEqualsWithDelta(64210, $result["impôt"], 1);
    $this->assertEqualsWithDelta(7498, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.45, $result["taux"]);
  }
 
  public function test11() {
    $result = $this->métier->calculerImpot("oui", 3, 200000);
    $this->assertEqualsWithDelta(42842, $result["impôt"], 1);
    $this->assertEqualsWithDelta(17283, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }
}

تعليقات

  • الأسطر 10–25: تحميل الملفات التي تحدد بيئة الاختبار. وهذا مماثل لما يحدث في طبقة [dao]؛
  • الأسطر 31–37: إنشاء مثيلات لطبقتي [dao] و[business]؛
  • الأسطر 40–47: اختبار حساب الضريبة؛
  • السطر 41: يتم إجراء حساب ضريبي محدد باستخدام طبقة [business]؛
  • الأسطر 42-46: التحقق من أن النتائج التي تم الحصول عليها تتطابق مع تلك التي تم الحصول عليها من محاكي مصلحة الضرائب [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm]؛
  • الأسطر 23-26: يتم إجراء اختبارات المساواة بدقة تصل إلى 1 يورو. في الواقع، لاحظنا أن مشكلات التقريب تسببت في أن ينتج خوارزمية الوثيقة النتائج المتوقعة بدقة تصل إلى 1 يورو؛
  • السطر 27: يتم حساب معدل الضريبة دون أي هامش للخطأ؛
  • الأسطر 49-137: يتكرر هذا النوع من الاختبارات 10 مرات، مع تكوين مختلف للمكلف في كل مرة؛

تسفر الاختبارات عن النتائج التالية:

Image

11.7.5. اختبارات الإصدارات المستقبلية

من الآن فصاعدًا، ستكون الاختبارات الخاصة بطبقتي [dao] و[business] مطابقة لتلك الموجودة في الإصدار 04. ولن يتغير سوى بيئة الاختبار. ولذلك، سنقدم فقط هذه البيئة ونتائج الاختبار.