8. تمرين تطبيقي – الإصدار 3
نعود إلى التمرين الذي درسناه سابقًا (الفقرتان 4.3 و4.4) لحله باستخدام كود PHP الذي يستخدم فئة.
8.1. شجرة البرامج النصية

8.2. الاستثناء [ExceptionImpots]
في الإصدار 03، عندما يواجه منشئ أو طريقة فئة خطأً ما، فإنها ستطلق استثناءً من النوع [ExceptionImpots] على النحو التالي:
تعليقات
- السطر 4: الفئة [ExceptionImpots] موجودة في مساحة الأسماء [Application]؛
- السطر 6: الفئة [ExceptionImpots] تمتد من الفئة المُعرَّفة مسبقًا في PHP [RuntimeException]؛
- السطر 8: يتوقع المنشئ معلمتين:
- $message: هي رسالة الخطأ المرتبطة بالاستثناء؛
- $code: هو رمز الخطأ المرتبط بالاستثناء. إذا لم يكن موجودًا، فسيتم استخدام الرمز 0؛
8.3. الفئة [TaxAdminData]
في الإصدار 02، تم تجميع بيانات مصلحة الضرائب:
- أولاً في ملف jSON؛
- ثم من هذا الملف jSON إلى جدول ترابطي؛
في الإصدار 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 من ملف التكوين في مصفوفة مرتبطة
$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;
}
…
}
تعليقات
- الأسطر 6-20: السمات التي ستستوعب السمات التي تحمل نفس الاسم في الملف jSON و[taxadmindata.json]. هذه نقطة مهمة: سمات الفئة [TaxAdminData] مطابقة لتلك الموجودة في الملفين jSON و[taxadmindata.json]. هذه الميزة تسهل كتابة الكود بشكل كبير؛
- لا تحتوي الفئة [TaxAdminData] على منشئ. وفي PHP، لا يمكن وجود أكثر من منشئ واحد. لذا فإن تحديد منشئ واحد يمنع تهيئة الكائن بطريقة أخرى. في ما يلي، لن تحتوي فئاتنا على منشئ، بل على عدة طرق من النوع [setFromQqChose] تسمح بتهيئتها بطرق مختلفة. يتم إنشاء كائن من النوع [TaxAdminData] باستخدام التعبير:
- السطر 23: تقوم الطريقة [setFromJsonFile] بتهيئة سمات الفئة باستخدام السمات التي تحمل نفس الاسم في الملف [$jsonFilename]؛
- الأسطر 24-42: يتم استخدام الملف jSON لإنشاء المصفوفة الترابطية [$arrayTaxAdminData]. وقد سبق أن صادفنا هذا الكود في البرنامج النصي [main.php] من الإصدار 02؛
- الأسطر 44-47: في حالة حدوث خطأ أثناء معالجة الملف jSON، يتم إثارة استثناء. وسيتم تمرير هذا الاستثناء إلى البرنامج النصي الرئيسي [main.php]؛
- الأسطر 48-51: يتم تهيئة سمات الفئة. ونستفيد هنا من حقيقة أن المصفوفة الترابطية [$arrayTaxAdminData] والفئة [TaxAdminData] لهما سمات تحمل نفس أسماء القيم الواردة من الملف jSON؛
- الأسطر 53-57: يتم التحقق من أن جميع سمات الفئة [TaxAdminData] قد تم تهيئتها؛
- السطر 53: يُرجع التعبير [get_object_vars($this)] مصفوفة مرتبطة تحتوي على سمات الكائن [$this]، وبالتالي سمات الفئة [TaxAdminData]. هنا يجب أن نفهم أن عملية التهيئة في الأسطر 48-51 قد أضافت سمات إلى الكائن [$this]. وبالتالي، إذا كتبنا:
فسيتم إضافة السمة [x] إلى الكائن [$this] حتى لو لم يتم إعلان هذه السمة في الفئة [TaxAdminData]. ما هو مؤكد هو أن سمات الأسطر 6-20 هي بالفعل جزء من الكائن [$this]، ولكن ربما لم يتم تهيئتها. هذا خطأ يسهل الوقوع فيه، يكفي أن تخطئ في اسم سمة في الملف [taxadmindata.json]؛
- الأسطر 54-57: يتم استعراض جميع سمات [$this]، وإذا لم يتم تهيئة إحداها، يتم إثارة استثناء؛
- يمكن تهيئة سمة بقيمة غير صحيحة. في PHP، لا يمكن تحديد نوع للسمات. وبالتالي فإن العملية:
ممكنة، في حين أن السمة [$plafondQfDemiPart] يجب أن تكون عددًا حقيقيًّا؛
- الأسطر 59-71: يتم التحقق من أن كل سمة من سمات الفئة لها قيمة عددية حقيقية موجبة أو صفرية. وتقوم الدالة [check] في السطر 76 بهذه المهمة. والمعلمة الخاصة بها [$value] هي إما قيمة واحدة أو مصفوفة من القيم؛
- السطر 62: تُرجع الدالة [check] كائنًا من النوع [\stdClass] مع سمتين:
- [erreur]: تساوي TRUE في حالة حدوث خطأ، وتساوي FALSE في حالة عدم وجود خطأ؛
- [value]: القيمة العددية الفعلية المطابقة للمعلمة [$value] التي تم تمريرها كمعلمة، السطر 62؛
- السطر 64: يتم التحقق مما إذا كانت عملية التحقق قد نجحت أم لا؛
- السطر 66: إذا لم يكن أحد السمات عددًا حقيقيًا موجبًا أو صفرًا، يتم إثارة استثناء؛
- السطر 69: وإلا، يتم تسجيل قيمته العددية؛
- السطر 73: يتم إرجاع الكائن [$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;
}
تعليقات
- السطر 1: المعلمة [$value] هي إما مصفوفة أو عنصر واحد. كما أن نوعه غير معروف. تأتي القيمة من الملف [taxadmindata.json]. وفقًا للقيم المسجلة في هذا الملف، يمكن أن تكون القيم المقروءة أعدادًا صحيحة أو أعدادًا حقيقية أو سلاسل أو قيمًا منطقية. على سبيل المثال:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
في الحالة 1، تكون القيمة من النوع [entier]، وفي الحالة 2 من النوع [réel]، وفي الحالة 3 تكون من النوع [string] والتي يمكن تحويلها إلى عدد، وفي الحالة 4 تكون من النوع [string] والتي لا يمكن تحويلها إلى عدد؛
- الأسطر 4-8: يتم إنشاء مصفوفة استنادًا إلى المعلمة [$value] التي تم استلامها كمعلمة في السطر 1؛
- السطر 10: المصفوفة التي سيتم ملؤها بأعداد حقيقية؛
- السطر 11: ستكون النتيجة كائنًا من النوع [\stdClass]؛
- السطر 13: تعبير علائقي لعدد حقيقي موجب أو صفر؛
- الأسطر 14-24: نتحقق من أن جميع عناصر المصفوفة [$tableau] هي أعداد حقيقية موجبة أو صفرية، ونملأ المصفوفة [$newTableau] بهذه العناصر المحولة إلى النوع [float] (السطر 17)؛
- الأسطر 18-23: بمجرد اكتشاف عنصر ليس عددًا حقيقيًا موجبًا أو صفرًا، يتم تسجيل الخطأ في النتيجة وإرجاعها؛
- الأسطر 25-34: الحالة التي تم فيها إعلان صحة جميع عناصر الجدول [$tableau]؛
- السطر 32: القيمة التي تم إرجاعها [$result→value] هي مصفوفة من الأعداد الحقيقية [float] أو عدد حقيقي واحد؛
تُرجع الدالة [__toString] الواردة في الأسطر 82-85 السلسلة jSON التي تحتوي على سمات وقيم الكائن [$this].
الأسطر 87-110: دالات الحصول (getters) والتعيين (setters) للفئة؛
ملاحظة: قد يكون من الصعب أحيانًا كتابة جميع وظائف get / set لفئة ما، خاصةً عندما يكون هناك الكثير من السمات. يمكن لـ Netbeans إنشاء هذه الوظائف تلقائيًا بالإضافة إلى المنشئ. للقيام بذلك، ما عليك سوى وضع السمات [1]:

- في [2]، وانقر بزر الماوس الأيمن في المكان الذي تريد إدراج الكود فيه، ثم اختر الخيار [Insert Code]؛

- إلى [4]، حدد أنك تريد إنشاء المنشئ؛
- في [5]، حدد جميع السمات: هذا يعني أنك تريد أن يكون للمنشئ معلمة لكل سمة؛
- في [6]، استخدم نمط منشئات Java؛
- في [7]، حدد أنك تريد صراحةً الكلمة الرئيسية [public] أمام المنشئ؛
- في [8]، قم بالتأكيد؛

- في [9]، قام Netbeans بإنشاء المنشئ. ومع ذلك، لم يتمكن من تحديد نوع المعلمات لأنه لا يعرفها. أضفها بنفسك [10]؛
لتوليد دالات الحصول (getters) والتعيين (setters)، كرر الخطوات من 2 إلى 4، وفي الخطوة 4، اختر [Getter and Setter]:

- في [5]، حدد أنك تريد getter و setter لكل سمة؛
- في [6]، حدد أنك تريد دالات القراءة والكتابة بالنمط المستخدم في Java: setAttribut، getAttribut؛
- في [7]، حدد أن تكون هذه الدوال العامة؛
- في [8]، قم بالتحقق من صحة البيانات؛

- في [9]، دالات الاسترجاع والتعيين التي أنشأتها NetBeans؛
احذف هذه الدوال وكرر الخطوات من 2 إلى 7.
- في [8]، حدد الخيار [Fluent Setter] الذي لم نحدده سابقًا؛
والنتيجة التي تم الحصول عليها هي كما يلي:

تنتهي كل دالة تعيين بعملية [return $this]. وهذا يسمح بتهيئة السمات على النحو التالي:
في الواقع، قيمة [$data→setLimites($limites)] (السطر 32 من الكود) هي [$this]، وبالتالي فهي هنا [$data]. وبالتالي، يمكن استدعاء الطريقة [setCoeffR($coeffR)] لهذا الكائن وهكذا دواليك، لأن هذه الطريقة بدورها تُنتج أيضًا [$this] (السطر 37 من الكود). يُطلق على أسلوب كتابة طرق الفئة هذا، الذي يجعل الطرق التي لا يُفترض أن تُرجع أي قيمة تُرجع الكائن [$this]، اسم «الكتابة المتدفقة». وهو يسهل استخدام هذه الطرق.
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;
}
تعليقات
- السطر 4: تم وضع الواجهة في مساحة الأسماء [Application]؛
- السطر 6: الواجهة التي تتيح حساب الضرائب؛
- السطر 10: ستسمح الطريقة [getTaxAdminData] باسترجاع البيانات من مصلحة الضرائب في كائن من النوع [TaxAdminData] الذي عرضناه للتو. نظرًا لأن هذه البيانات قد تكون موجودة في ملف أو قاعدة بيانات أو حتى على الشبكة، فقد تفشل الطريقة [getTaxAdminData] في الحصول على البيانات. في هذه الحالة، ستطلق استثناءً من النوع [ExceptionImpots]. هذه هي الطريقة القياسية في البرمجة الكائنية للإبلاغ عن خطأ وقع في طريقة أو منشئ؛
- السطر 13: ستسمح الطريقة [calculerImpot] بحساب الضريبة الخاصة بمستخدم ما؛
- السطر 20: ستسمح الطريقة [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
تجدر الإشارة إلى أن السطرين 5 و7 يحتويان على عناصر خاطئة.
سيكون محتوى الملف النصي [$resultsFileName] كما يلي:
ومحتوى الملف النصي [$errorsFileName] سيكون كما يلي:
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--;
}
// النهاية - إعادة السطر
return($ligne);
}
}
تعليقات
- السطر 4: يتم وضع الفئة [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 {
…
}
}
تعليقات
- السطر 4: ستكون الفئة [AbstractBaseImpots] ضمن مساحة الأسماء [Application] مثل العناصر الأخرى في التطبيق قيد الكتابة؛
- السطر 7: الفئة [AbstractBaseImpots] تنفذ الواجهة [InterfaceImpots]؛
- السطر 9: سيتم وضع بيانات إدارة الضرائب في السمة [$taxAdminData]؛
- السطر 12: تنفيذ الطريقة [getTaxAdminData] الخاصة بالواجهة. لا يمكننا بعد تحديد هذه الطريقة: فقد رأينا مثالاً في الفقرة السابقة حيث تم استخراج بيانات مصلحة الضرائب من ملف jSON. وسنرى حالة أخرى حيث سيتم البحث عن البيانات في قاعدة بيانات. وسيكون على الفئات المشتقة تعريف محتوى الطريقة [getTaxAdminData]. وستؤدي الحالتان السابقتان إلى إنشاء فئتين مشتقتين. وبالتالي، يتم إعلان الطريقة [getTaxAdminData] على أنها طريقة مجردة، مما يجعل الفئة نفسها مجردة تلقائيًا (السطر 7)؛
- الأسطر 15-64: وظيفة حساب الضريبة التي سبق ذكرها في الفقرتين [رابط] و[رابط]؛
- كانت النسخة 02 تضع بيانات مصلحة الضرائب في جدول مرتبط [$taxAdminData]. أما النسخة 03 فتضعها في السمة [$this→taxAdminData]. الفرق الأول بين هذين الحلين هو اختلاف في رؤية البيانات الضريبية:
- في الإصدار 02، لم يكن للجدول الترابطي [$taxAdminData] رؤية شاملة. لذلك تم تمريره كمعلمة إلى جميع وظائف حساب الضريبة؛
- في الإصدار 03، يتمتع السمة [$this→taxAdminData] برؤية شاملة لجميع أساليب الفئة. وبالتالي، لا يتم تمريره كمعلمة إلى جميع دوال حساب الضريبة؛
- ويكمن الاختلاف الثاني في أن الإصدار 03 يستبدل الدوال بأساليب الفئة. ويتم الآن استدعاء كل أسلوب باستخدام تعبير [$this→getMéthode(…)] (الأسطر 27 و31 و57 و60)؛
- والاختلاف الثالث هو أنه عندما تبدأ الطريقة [calculerImpot] عملها، فإنها لا تعرف ما إذا كان السمة [private $taxAdminData] التي تحتاجها قد تم تهيئتها أم لا. في الواقع، لا يقوم مُنشئ الفئة بتهيئتها. لذلك، يقع على عاتق الطريقة [calculerImpot] القيام بذلك باستخدام الطريقة [getTaxAdminData] في السطر 12. وهذا ما يتم في الأسطر 23-25؛
- وبصرف النظر عن هذه الاختلافات، تظل طرق حساب الضريبة كما كانت في الإصدارات السابقة؛
الوظيفة [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);
// نستخرج الحقول الثلاثة «متزوج:أطفال:راتب» التي تشكل $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);
}
}
تعليقات على الكود
- السطر 1: تتلقى الدالة ثلاثة معلمات:
- [$usersFileName]: اسم الملف النصي الذي يحتوي على بيانات دافعي الضرائب. يحتوي كل سطر نصي على بيانات دافع ضرائب واحد بالشكل التالي: الحالة الاجتماعية (نعم/لا)، عدد الأطفال، الراتب السنوي:
- (تابع)
- [$resultsFileName]: اسم الملف النصي الذي سيحتوي على النتائج. سيكون كل سطر نصي بالصيغة التالية:
- (تابع)
- [$errorsFileName]: اسم ملف النص الخاص بالأخطاء:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- السطر 3: نظرًا لأن عددًا من العمليات قد تؤدي إلى حدوث استثناء، فإن عبارة try / catch / finally تحيط بكامل كود الأسلوب؛
- الأسطر 3-19: يتم فتح الملفات الثلاثة. يتم إثارة استثناء فور فشل عملية فتح أي ملف؛
- السطر 24: تُقرأ أسطر ملف [$data] واحدة تلو الأخرى بحد أقصى 100 حرف (جميع الأسطر أقل من 100 حرف)؛
- السطر 28: تُستخدم الطريقة الثابتة [Utilitaires::cutNewLineChar] لإزالة علامة نهاية السطر إن وجدت؛
- السطر 30: يتم استرداد العناصر الثلاثة من السطر الذي تمت قراءته؛
- الأسطر 33-52: يتم التحقق من صحة العناصر الثلاثة. هنا، لا يتم إثارة استثناء في حالة وجود خطأ، بل يتم تدوين رسالة الخطأ في الملف النصي [$errors] (السطر 55)؛
- السطر 59: إذا كان السطر الذي تمت قراءته صالحًا، يتم حساب الضريبة. ونحصل على نتيجة في شكل جدول ترابطي ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux]؛
- السطر 61: تُضاف المفاتيح [marié, enfants, salaire] إلى النتيجة التي تم الحصول عليها؛
- السطر 61: يتم تسجيل النتيجة في ملف نصي [$results] في شكل سلسلة jSON للنتيجة التي تم الحصول عليها؛
- الأسطر 68-70: في نهاية معالجة الملف [$data]، يتم فحص عدد الأسطر الخاطئة التي تم العثور عليها. إذا كان هناك خطأ واحد على الأقل، يتم إلقاء استثناء؛
- الأسطر 71-74: يتم اعتراض الاستثناء الذي قد يكون أطلقه الكود وإعادة إطلاقه على الفور (السطر 73). الهدف من هذه الحيلة هو الحصول على جملة [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;
}
}
تعليقات
- السطر 7: الفئة [ImpotsWithTaxAdminDataInJsonFile] تمتد من الفئة المجردة [AbstractBaseImpots]. وسيتعين عليها تعريف الطريقة [getTaxAdminData] التي لم تعرّفها الفئة الأم؛
- السطر 9: سيحتوي السمة [$taxAdminData] على بيانات مصلحة الضرائب؛
- الأسطر 12-15: يتلقى المنشئ كمعلمة وحيدة اسم الملف jSON الذي يحتوي على البيانات الضريبية؛
- السطر 14: يتم إنشاء كائن من النوع [TaxAdminData] ثم تهيئته. قد تؤدي هذه العملية إلى إثارة استثناء من النوع [ExceptionImpots]. وسيتم تمرير هذا الاستثناء إلى البرنامج النصي الرئيسي [main.php]؛
- الأسطر 18-20: يتم إعطاء نص للطريقة [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";
// اختبار -----------------------------------------------------
// تعريف الثوابت
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();
تعليقات
- السطر 4: يُفرض الالتزام الصارم بأنواع معلمات الدوال؛
- السطر 7: يتم أيضًا وضع البرنامج النصي [main.php] في مساحة الأسماء [Application]؛
- الأسطر 10-15: يتم إرشاد مترجم PHP إلى مواقع الفئات والواجهات التي يستخدمها البرنامج النصي. تجدر الإشارة إلى أننا لم نستخدم هنا تعليمة use لإعلان الاسم الكامل للفئات المستخدمة في البرنامج النصي. وهذا غير ضروري في الواقع لأن البرنامج النصي والفئات موجودة في نفس مساحة الأسماء [Application]؛
- الأسطر 18-22: أسماء الملفات النصية المستخدمة في البرنامج النصي؛
- الأسطر 24-29: يتم إنشاء كائن [ImpotsWithTaxAdminDataInJsonFile] ومعالجة أي استثناء محتمل؛
- السطر 28: يتم تنفيذ الطريقة [executeBatchImpots] التي ستقوم بحساب الضرائب لجميع دافعي الضرائب في الملف [TAXPAYERSDATA_FILENAME]. سيتم تخزين النتائج في الملف [RESULTS_FILENAME]، أما الأخطاء المحتملة فستُخزّن في الملف [ERRORS_FILENAME]؛
- السطور 29-32: في حالة حدوث خطأ لا يمكن إصلاحه، يتم عرض رسالة الخطأ؛
النتائج
مع ملف دافعي الضرائب [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] التالي: