13. تمرين تطبيقي – الإصدار 5

لقد كتبنا بالفعل عدة إصدارات من هذا التمرين. استخدم الإصدار الأخير بنية متعددة الطبقات:

تقوم الطبقة [dao] بتنفيذ واجهة [InterfaceDao]. وقد أنشأنا فئةً تنفذ هذه الواجهة:
- [DaoImpotsWithTaxAdminDataInJsonFile] التي كانت تستخرج البيانات الضريبية من ملف jSON؛
سنقوم بتنفيذ الواجهة [InterfaceDao] من خلال فئة جديدة هي [DaoImpotsWithTaxAdminDataInDatabase] التي ستقوم باسترداد بيانات مصلحة الضرائب من قاعدة بيانات MySQL.
13.1. إنشاء قاعدة البيانات [dbimpots-2019]
باتباع المثال الوارد في الفقرة المرتبطة، نقوم بإنشاء قاعدة بيانات MySQL باسم [dbimpots-2019]، وسيكون مالكها هو [admimpots] بكلمة مرور [mdpimpots]:

- في [1-4] أعلاه، نرى قاعدة البيانات [dbimpots-2019] التي لا تحتوي على أي جداول في الوقت الحالي؛

- في [1-5] أعلاه، نرى أن المستخدم [admimpots] لديه جميع الحقوق على قاعدة البيانات [dbimpots-2019]. ما لا نراه هنا هو أن هذا المستخدم لديه كلمة المرور [admimpots]؛
نقوم الآن بإنشاء الجدول [tbtranches] الذي سيحتوي على شرائح الضرائب:

- في [1-7]، نقوم بإنشاء جدول باسم [tbtranches] يحتوي على 4 أعمدة؛

- في [3-6]، نحدد عمودًا باسم [id] (3)، من النوع الصحيح [int] (4)، والذي سيكون المفتاح الأساسي [6] للجدول وسيتم زيادة قيمته تلقائيًا [5] بواسطة SGBD. وهذا يعني أن MySQL ستتولى بنفسها إدارة قيم المفتاح الأساسي عند عمليات الإدراج. وستعيّن القيمة 1 للمفتاح الأساسي في عملية الإدراج الأولى، ثم 2 في العملية التالية، وهكذا دواليك؛
- في [7]، يعرض علينا المساعد خيارات أخرى لتكوين المفتاح الأساسي. هنا نكتفي بتأكيد [7] القيم الافتراضية؛

- في [8-16]، نحدد الأعمدة الثلاثة الأخرى في الجدول:
- [limites] (8) من النوع العشري (9) المكون من 10 أرقام منها رقمان عشريان (10) سيحتوي على عناصر العمود 17 الخاص بشرائح الضرائب؛
- [coeffR] (11) من النوع العشري (12) مكون من 6 أرقام منها رقمان عشريان (13) سيحتوي على عناصر العمود 18 من شرائح الضرائب؛
- [coeffN] (14) من نوع الرقم العشري (15) المكون من 10 أرقام منها رقمان عشريان (16) سيحتوي على عناصر العمود 19 من شرائح الضرائب؛
بعد التحقق من صحة هذه البنية، نحصل على النتيجة التالية:

- في [5]، تشير أيقونة المفتاح إلى أن العمود [id] هو مفتاح أساسي. ونلاحظ أيضًا أن هذا المفتاح الأساسي له قيم صحيحة (6) وأنه يُدار (يتم زيادة قيمته تلقائيًا) بواسطة MySQL؛
وبنفس الطريقة التي أنشأنا بها الجدول [tbtranches]، نقوم بإنشاء الجدول [tbconstantes] الذي سيحتوي على الثوابت الخاصة بحساب الضريبة:

يمكن تصدير بنية قاعدة البيانات إلى ملف نصي على شكل سلسلة من الأوامر SQL:

لا تقوم الخيار [5] هنا بتصدير سوى بنية قاعدة البيانات وليس محتواها. في حالتنا، لا تحتوي قاعدة البيانات على أي محتوى بعد.



ينتج الخيار [11] الملف SQL [dbimpots-2019.sql] التالي:
-- phpMyAdmin SQL تفريغ
-- الإصدار 4.8.5
-- https://www.phpmyadmin.net/
--
-- المضيف: localhost:3306
-- وقت الإنشاء: 30 يونيو 2019 الساعة 01:10 PM
-- إصدار الخادم: 5.7.24
-- إصدار PHP: 7.2.11
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- قاعدة البيانات: `dbimpots-2019`
--
CREATE DATABASE IF NOT EXISTS `dbimpots-2019` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `dbimpots-2019`;
-- --------------------------------------------------------
--
-- هيكل الجدول لجدول `tbconstantes`
--
DROP TABLE IF EXISTS `tbconstantes`;
CREATE TABLE `tbconstantes` (
`id` int(11) NOT NULL,
`plafondQfDemiPart` decimal(10,2) NOT NULL,
`plafondRevenusCelibatairePourReduction` decimal(10,2) NOT NULL,
`plafondRevenusCouplePourReduction` decimal(10,2) NOT NULL,
`valeurReducDemiPart` decimal(10,2) NOT NULL,
`plafondDecoteCelibataire` decimal(10,2) NOT NULL,
`plafondDecoteCouple` decimal(10,2) NOT NULL,
`plafondImpotCelibatairePourDecote` decimal(10,2) NOT NULL,
`plafondImpotCouplePourDecote` decimal(10,2) NOT NULL,
`abattementDixPourcentMax` decimal(10,2) NOT NULL,
`abattementDixPourcentMin` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- هيكل الجدول `tbtranches`
--
DROP TABLE IF EXISTS `tbtranches`;
CREATE TABLE `tbtranches` (
`id` int(11) NOT NULL,
`limites` decimal(10,2) NOT NULL,
`coeffR` decimal(10,2) NOT NULL,
`coeffN` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- فهارس الجداول التي تم تصديرها
--
--
-- الفهارس الخاصة بالجدول `tbconstantes`
--
ALTER TABLE `tbconstantes`
ADD PRIMARY KEY (`id`);
--
-- فهارس الجدول `tbtranches`
--
ALTER TABLE `tbtranches`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT للجداول التي تم تصديرها
--
--
-- AUTO_INCREMENT للجدول `tbconstantes`
--
ALTER TABLE `tbconstantes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT للجدول `tbtranches`
--
ALTER TABLE `tbtranches`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
يمكنك استخدام هذا الملف SQL لإعادة إنشاء قاعدة البيانات [dbimpots-2019] في حالة تلفها أو تعرضها للتغيير. ليس هناك داعٍ هنا لحذف قاعدة البيانات قبل إعادة إنشائها، حيث إن البرنامج النصي SQL يتولى القيام بذلك بنفسه:


13.2. تنظيم الكود
لتوضيح دور البرامج النصية المختلفة PHP التي نكتبها بشكل أفضل، سنقوم بتنظيم الكود في مجلدات:

- في [1]، نظرة عامة على الإصدار 05؛
- في [2]، كيانات التطبيق، الكيانات المتبادلة بين الطبقات؛
- في [3]، أدوات التطبيق؛
- في [4]، البيانات المستخدمة أو التي ينتجها التطبيق. وقد قررنا هنا استخدام ملفات jSON فقط للملفات النصية. وتتميز هذه الملفات بعدة مزايا:
- تتعرف عليها العديد من الأدوات؛
- تتميز هذه الأدوات بخاصية تمييز الألوان النحوية. علاوة على ذلك، فإن صيغة jSON تخضع لقواعد معينة. وعندما لا يتم الالتزام بهذه القواعد، تقوم الأدوات بالإبلاغ عن ذلك. على سبيل المثال، من الأخطاء التي يصعب اكتشافها في ملف نصي بسيط استخدام الحرف O كبير أو صغير بدلاً من الأصفار. إذا حدث هذا الخطأ، فسيتم الإبلاغ عنه. فعلى سبيل المثال، في الكود jSON:
"plafondRevenusCouplePourReduction": 42O74
حيث تم عن غير قصد وضع حرف O كبير بدلاً من الصفر في [42074]، يقوم NetBeans بالإبلاغ عن الخطأ:

في الواقع، يتعرف NetBeans على الحرف O الكبير الذي يجعل من [49O74] سلسلة أحرف. ويستنتج من ذلك أن الصيغة الصحيحة يجب أن تكون [4-5]: أي أن السلسلة [47O74] يجب أن تكون محاطة بعلامات اقتباس. وبالتالي، يتم لفت انتباه المطور إلى الخطأ ويمكنه تصحيحه: إما بإضافة علامتي الاقتباس، أو باستبدال الحرف «O» بالرقم صفر؛
العناصر الأخرى في الإصدار 05 هي كما يلي:

- في [6]، واجهات وفئات الطبقة [Dao]؛
- في [7]، واجهات وفئات الطبقة [métier]؛
- في [8]، البرامج النصية الرئيسية للإصدار 05؛
الإصدار 05 له هدفان متميزان:
- ملء قاعدة البيانات MySQL [dbimpots-2019] بمحتوى الملف jSON [Data/txadmindata.json]؛
- تنفيذ حساب الضريبة باستخدام البيانات الضريبية المستمدة من قاعدة البيانات MySQL و [dbimpots-2019]؛
سنعالج هذين الهدفين بشكل منفصل.
13.3. ملء قاعدة البيانات [dbimpots-2019]
13.3.1. الهدف
يحتوي الملف النصي 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
}
هدفنا هو نقل هذه البيانات إلى قاعدة البيانات MySQL [dbimpots-2019] التي تم إنشاؤها مسبقًا.
13.3.2. الكيانات

ستُستخدم الكيان [Database] لتغليف بيانات الملف jSON [database.json] التالي:
{
"dsn": "mysql:host=localhost;dbname=dbimpots-2019",
"id": "admimpots",
"pwd": "mdpimpots",
"tableTranches": "tbtranches",
"colLimites": "limites",
"colCoeffR": "coeffr",
"colCoeffN": "coeffn",
"tableConstantes": "tbconstantes",
"colPlafondQfDemiPart": "plafondQfDemiPart",
"colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
"colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
"colValeurReducDemiPart": "valeurReducDemiPart",
"colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
"colPlafondDecoteCouple": "plafondDecoteCouple",
"colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
"colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
"colAbattementDixPourcentMax": "abattementDixPourcentMax",
"colAbattementDixPourcentMin": "abattementDixPourcentMin"
}
ستُستخدم الكيان [TaxAdminData] لتغليف بيانات الملف jSON [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
}
ستُستخدم الكيان [TaxPayerData] لتغليف بيانات الملف jSON و [taxpayerdata.json] التاليين:
[
{
"marié": "oui",
"enfants": 2,
"salaire": 55555
},
{
"marié": "ouix",
"enfants": "2x",
"salaire": "55555x"
},
{
"marié": "oui",
"enfants": "2",
"salaire": 50000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 50000
},
{
"marié": "non",
"enfants": 2,
"salaire": 100000
},
{
"marié": "non",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 5,
"salaire": 100000
},
{
"marié": "non",
"enfants": 0,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 2,
"salaire": 30000
},
{
"marié": "non",
"enfants": 0,
"salaire": 200000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 20000
}
]
13.3.2.1. الفئة الأساسية [BaseEntity]
لتبسيط كود الكيانات، سنتبع القاعدة التالية: تحمل سمات الكيان نفس أسماء سمات الملف jSON الذي يجب أن يقوم الكيان بتغليفه. بموجب هذه القاعدة، فإن الكيانات [Database, TaxAdminData, TaxPayerData] لها نقاط مشتركة يمكن تحويلها إلى فئة أصلية. وستكون هذه هي الفئة [BaseEntity] التالية:
<?php
namespace Application;
class BaseEntity {
// السمة
protected $arrayOfAttributes;
// التهيئة من ملف jSON
public function setFromJsonFile(string $jsonFilename) {
// يتم استرداد محتوى ملف البيانات الضريبية
$fileContents = \file_get_contents($jsonFilename);
$erreur = FALSE;
// خطأ؟
if (!$fileContents) {
// تسجيل الخطأ
$erreur = TRUE;
$message = "Le fichier des données [$jsonFilename] n'existe pas";
}
if (!$erreur) {
// استرداد الرمز jSON من ملف التكوين في جدول مرتبط
$this->arrayOfAttributes = \json_decode($fileContents, true);
// خطأ؟
if ($this->arrayOfAttributes === FALSE) {
// يتم تسجيل الخطأ
$erreur = TRUE;
$message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
}
}
// خطأ؟
if ($erreur) {
// يتم إثارة استثناء
throw new ExceptionImpots($message);
}
// تهيئة سمات الفئة
foreach ($this->arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// إرجاع الكائن
return $this;
}
public function checkForAllAttributes() {
// التحقق من تهيئة جميع المفاتيح
foreach (\array_keys($this->arrayOfAttributes) as $key) {
if ($key !== "arrayOfAttributes" && !isset($this->$key)) {
throw new ExceptionImpots("L'attribut [$key] de la classe "
. get_class($this) . " n'a pas été initialisé");
}
}
}
public function setFromArrayOfAttributes(array $arrayOfAttributes) {
// يتم تهيئة بعض سمات الفئة
foreach ($arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// إرجاع الكائن
return $this;
}
// toString
public function __toString() {
// سمات الكائن
$arrayOfAttributes = \get_object_vars($this);
// يتم إزالة السمة من الفئة الأم
unset($arrayOfAttributes["arrayOfAttributes"]);
// سلسلة Json للكائن
return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
}
// مُستخرج
public function getArrayOfAttributes() {
return $this->arrayOfAttributes;
}
}
تعليقات
- السطر 5: الفئة [BaseEntity] مخصصة للتوسيع بواسطة الفئات [Database, TaxAdminData, TaxPayerData]؛
- السطر 7: السمة [$arrayOfAttributes] عبارة عن مصفوفة تحتوي على جميع سمات الفئة الفرعية التي وسعت [BaseEntity] بالإضافة إلى قيمها؛
- الأسطر 9-41: يتم تهيئة السمة [$arrayOfAttributes] انطلاقًا من الملف jSON [$jsonFilename] الذي تم تمريره كمعلمة. يتم إلقاء استثناء من النوع [ExceptionImpot] إذا تعذر قراءة الملف jSON أو إذا لم يكن ملفًا صالحًا من النوع jSON؛
- الأسطر 36-38: يوجد هنا كود خاص إذا تم تنفيذه بواسطة فئة فرعية. في هذه الحالة، يمثل [$this] مثيلًا للفئة الفرعية [Database, TaxAdminData, TaxPayerData]، وفي هذه الحالة، تقوم الأسطر 36-38 بتهيئة سمات هذه الفئة الفرعية، بشرط أن تكون هذه السمات ذات مستوى رؤية «محمي» (أو «عام») (انظر الفقرة المرتبطة). فقد ذكرنا بالفعل أن سمات الكيانات [Database, TaxAdminData, TaxPayerData] هي نفس سمات الملف jSON الذي تغلفه. وأخيرًا، تسمح الطريقة [setFromJsonFile] لفئة فرعية بالتهيئة من ملف jSON؛
- السطر 40: يتم إرجاع الكائن [$this]، أي مثيل لفئة فرعية، إذا تم استدعاء الأسلوب [setFromJsonFile] بواسطة فئة فرعية؛
- السطور 43-51: تسمح الطريقة [checkForAllAttributes] لفئة فرعية بالتحقق من أن جميع سماتها قد تم تهيئتها. إذا لم يكن الأمر كذلك، يتم إلقاء استثناء [ExceptionImpots]. تسمح هذه الطريقة للفئة الفرعية بالتحقق من أن ملفها jSON لم يغفل بعض السمات؛
- الأسطر 53-60: تسمح الطريقة [setFromArrayOfAttributes] لفئة فرعية بتهيئة كل أو بعض سماتها انطلاقًا من مصفوفة مرتبطة تحتوي على مفاتيح تحمل نفس أسماء سمات الفئة الفرعية المراد تهيئتها؛
- الأسطر 63-70: تسمح الطريقة [__toString] بالحصول على تمثيل jSON لفئة فرعية؛
13.3.2.2. الكيان [Database]
الكيان [Database] هو التالي:
<?php
namespace Application;
class Database extends BaseEntity {
// السمات
protected $dsn;
protected $id;
protected $pwd;
protected $tableTranches;
protected $colLimites;
protected $colCoeffR;
protected $colCoeffN;
protected $tableConstantes;
protected $colPlafondQfDemiPart;
protected $colPlafondRevenusCelibatairePourReduction;
protected $colPlafondRevenusCouplePourReduction;
protected $colValeurReducDemiPart;
protected $colPlafondDecoteCelibataire;
protected $colPlafondDecoteCouple;
protected $colPlafondImpotCelibatairePourDecote;
protected $colPlafondImpotCouplePourDecote;
protected $colAbattementDixPourcentMax;
protected $colAbattementDixPourcentMin;
…
}
تُستخدم الفئة [Database] لتغليف بيانات الملف jSON [database.json] التالي:
{
"dsn": "mysql:host=localhost;dbname=dbimpots-2019",
"id": "admimpots",
"pwd": "mdpimpots",
"tableTranches": "tbtranches",
"colLimites": "limites",
"colCoeffR": "coeffr",
"colCoeffN": "coeffn",
"tableConstantes": "tbconstantes",
"colPlafondQfDemiPart": "plafondQfDemiPart",
"colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
"colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
"colValeurReducDemiPart": "valeurReducDemiPart",
"colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
"colPlafondDecoteCouple": "plafondDecoteCouple",
"colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
"colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
"colAbattementDixPourcentMax": "abattementDixPourcentMax",
"colAbattementDixPourcentMin": "abattementDixPourcentMin"
}
تتمتع الفئة والملف jSON بنفس السمات. وتصف هذه السمات خصائص قاعدة البيانات MySQL و[dbimpots-2019]:
dsn | اسم قاعدة البيانات DSN |
id | مالك قاعدة البيانات |
pwd | كلمة المرور الخاصة به |
tableTranches | اسم الجدول الذي يحتوي على شرائح الضرائب |
colLimites colCoeffR colCoeffN | أسماء أعمدة الجدول [tableTranches] |
tableConstantes | اسم الجدول الذي يحتوي على ثوابت حساب الضريبة |
colPlafondQfDemiPart colPlafondRevenusCelibatairePourReduction colPlafondRevenusCouplePourReduction colValeurReducDemiPart colPlafondDecoteCelibataire colPlafondDecoteCouple colPlafondImpotCelibatairePourDecote colPlafondImpotCouplePourDecote colAbattementDixPourcentMax colAbattementDixPourcentMin | أسماء أعمدة الجدول [tableConstantes] الذي يحتوي على ثوابت حساب الضريبة |
لماذا يتم تسمية الجداول والأعمدة في حين أننا نعرف أسمائها بالفعل وأنها لن تتغير؟ بعد SGBD و MySQL، سنستخدم SGBD و PostgreSQL لتخزين بيانات الإدارة الضريبية. إلا أن أسماء الأعمدة والجداول في Postgres لا تتبع نفس القواعد التي يتبعها MySQL. سنضطر إلى استخدام أسماء أخرى. وينطبق الأمر نفسه على قواعد البيانات الأخرى مثل SGBD. وإذا أردنا الحصول على كود قابل للنقل بين قواعد البيانات المختلفة، فمن الأفضل استخدام المعلمات بدلاً من الأسماء الثابتة للجداول والأعمدة.
لنعد إلى كود الفئة [Database]:
<?php
namespace Application;
class Database extends BaseEntity {
// السمات
protected $dsn;
protected $id;
protected $pwd;
protected $tableTranches;
protected $colLimites;
protected $colCoeffR;
protected $colCoeffN;
protected $tableConstantes;
protected $colPlafondQfDemiPart;
protected $colPlafondRevenusCelibatairePourReduction;
protected $colPlafondRevenusCouplePourReduction;
protected $colValeurReducDemiPart;
protected $colPlafondDecoteCelibataire;
protected $colPlafondDecoteCouple;
protected $colPlafondImpotCelibatairePourDecote;
protected $colPlafondImpotCouplePourDecote;
protected $colAbattementDixPourcentMax;
protected $colAbattementDixPourcentMin;
// مُعيّن
// التهيئة
public function setFromJsonFile(string $jsonFilename) {
// الأصل
parent::setFromJsonFile($jsonFilename);
// التحقق من تهيئة جميع السمات
parent::checkForAllAttributes();
// إرجاع الكائن
return $this;
}
// وظائف الاسترجاع والتعيين
public function getDsn() {
return $this->dsn;
}
…
public function setDsn($dsn) {
$this->dsn = $dsn;
return $this;
}
…
}
تعليقات
- الأسطر 7-24: جميع سمات الفئة لها مستوى الرؤية [protected]. وهذا هو الشرط اللازم لتعديلها من الفئة الأم [BaseEntity] (انظر الفقرة «الرابط»);
- الأسطر 28-35: تسمح الطريقة [setFromJsonFile] بتهيئة سمات الفئة [Database] انطلاقًا من محتوى ملف jSON الذي يتم تمريره كمعلمة. يجب أن تكون سمات الملف jSON وسمات الفئة [Database] متطابقة. إذا كان الملف jSON غير قابل للاستخدام، يتم إلقاء استثناء؛
- السطر 30: تقوم الفئة الأم بعملية التهيئة؛
- السطر 32: يُطلب من الفئة الأم التحقق من أن جميع سمات الفئة [Database] قد تمت تهيئتها. إذا لم يكن الأمر كذلك، يتم إلقاء استثناء؛
- السطر 34: يتم إرجاع مثيل [Database] الذي تم تهيئته للتو؛
- السطر 37 وما بعده: دالات الحصول (getters) والتعيين (setters) لسمات الفئة؛
13.3.2.3. الكيان [TaxAdminData]
الكيان [TaxAdminData] هو كما يلي:
<?php
namespace Application;
class TaxAdminData extends BaseEntity {
// شرائح الضريبة
protected $limites;
protected $coeffR;
protected $coeffN;
// ثوابت حساب الضريبة
protected $plafondQfDemiPart;
protected $plafondRevenusCelibatairePourReduction;
protected $plafondRevenusCouplePourReduction;
protected $valeurReducDemiPart;
protected $plafondDecoteCelibataire;
protected $plafondDecoteCouple;
protected $plafondImpotCouplePourDecote;
protected $plafondImpotCelibatairePourDecote;
protected $abattementDixPourcentMax;
protected $abattementDixPourcentMin;
…
}
تُستخدم الفئة [TaxAdminData] لتغليف بيانات الملف jSON [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
}
تتمتع الفئة والملف jSON بنفس السمات. وتمثل هذه البيانات بيانات إدارة الضرائب. أما باقي كود الفئة [TaxAdminData] فهو كما يلي:
<?php
namespace Application;
class TaxAdminData extends BaseEntity {
// شرائح الضريبة
protected $limites;
protected $coeffR;
protected $coeffN;
// ثوابت حساب الضريبة
protected $plafondQfDemiPart;
protected $plafondRevenusCelibatairePourReduction;
protected $plafondRevenusCouplePourReduction;
protected $valeurReducDemiPart;
protected $plafondDecoteCelibataire;
protected $plafondDecoteCouple;
protected $plafondImpotCouplePourDecote;
protected $plafondImpotCelibatairePourDecote;
protected $abattementDixPourcentMax;
protected $abattementDixPourcentMin;
// التهيئة
public function setFromJsonFile(string $taxAdminDataFilename) {
// الأصل
parent::setFromJsonFile($taxAdminDataFilename);
// التحقق من تهيئة جميع السمات
parent::checkForAllAttributes();
// التحقق من أن قيم السمات هي أعداد حقيقية >=0
foreach ($this as $key => $value) {
if ($key !== "arrayOfAttributes") {
// يجب أن يكون $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;
}
protected function check($value): \stdClass {
// $value هو مصفوفة من العناصر من نوع string أو عنصر واحد
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;
}
// وظائف الحصول والتعيين
…
}
تعليقات
- السطر 23: تُستخدم الطريقة [setFromJsonFile] لتهيئة سمات الفئة [TaxAdminData] انطلاقًا من ملف jSON الذي يتم تمريره كمعلمة. يجب أن تكون سمات الملف jSON موجودة بنفس الاسم في الفئة؛
- السطر 25: الفئة الأم هي التي تقوم بهذه المهمة؛
- السطر 27: يُطلب من الفئة الأم التحقق من أن جميع سمات الفئة الفرعية قد تم تهيئتها؛
- الأسطر 29-42: يتم التحقق محليًّا من أن جميع السمات قد أُعطيت قيمة حقيقية موجبة أو صفرية. وقد تمت مناقشة هذا التحقق سابقًا في الفقرة «الرابط» من الإصدار 03؛
13.3.3. الطبقة [dao]
الآن يمكننا كتابة الكود الذي سيقوم بنقل البيانات من الملف النصي [taxadmindata.json] إلى الجداول [tbtranches, tbconstantes] في قاعدة البيانات MySQL [dbimpots-2019]. وسنتبنى البنية التالية:


ستقوم الطبقة [dao] بتنفيذ الواجهة [InterfaceDao4TransferAdminDataFromFile2Database] التالية:
<?php
// مساحة الأسماء
namespace Application;
interface InterfaceDao4TransferAdminData2Database {
public function transferAdminData2Database(): void;
}
تعليقات
- السطر 8: تتمثل وظيفة الطريقة [transferAdminData2Database] في تخزين بيانات مصلحة الضرائب في قاعدة بيانات؛
سيتم تنفيذ الواجهة [InterfaceDao4TransferAdminData2Database] بواسطة الفئة [DaoTransferAdminDataFromJsonFile2Database] التالية:
<?php
// مساحة الأسماء
namespace Application;
// تعريف فئة TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
// سمات قاعدة البيانات المستهدفة
private $database;
// بيانات إدارة الضرائب
private $taxAdminData;
// الشركة المصنعة
public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
// يتم حفظ تكوين قاعدة البيانات
$this->database = (new Database())->setFromJsonFile($databaseFilename);
// يتم حفظ البيانات الضريبية
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
}
// نقل بيانات شرائح الضرائب من ملف نصي
// إلى قاعدة البيانات
public function transferAdminData2Database(): void {
// العمل على قاعدة البيانات
$database = $this->database;
try {
// يتم فتح الاتصال بقاعدة البيانات
$connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
// نريد أن يتم إطلاق استثناء عند كل خطأ في SGBD
$connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// نبدأ معاملة
$connexion->beginTransaction();
// نقوم بملء جدول شرائح الضرائب
$this->fillTableTranches($connexion);
// يتم ملء جدول الثوابت
$this->fillTableConstantes($connexion);
// يتم إنهاء المعاملة بنجاح
$connexion->commit();
} catch (\PDOException $ex) {
// هل هناك معاملة جارية؟
if (isset($connexion) && $connexion->inTransaction()) {
// إنهاء المعاملة بعد الفشل
$connexion->rollBack();
}
// إعادة الاستثناء إلى الكود المستدعي
throw new ExceptionImpots($ex->getMessage());
} finally {
// يتم إغلاق الاتصال
$connexion = NULL;
}
}
// ملء جدول شرائح الضرائب
private function fillTableTranches($connexion): void {
…
}
// ملء جدول الثوابت
private function fillTableConstantes($connexion): void {
…
}
}
تعليقات
نستخدم هنا ما تعلمناه في الفصل الخاص بـ MySQL.
- السطر 7: الفئة [DaoTransferAdminDataFromJsonFile2Database] تنفذ الواجهة [InterfaceDao4TransferAdminData2Database]؛
- السطر 9: السمة [$database] هي كائن من النوع [Database] الذي يغلف بيانات الملف [database.json]؛
- السطر 11: السمة [$taxAdminData] هي كائن من النوع [TaxAdminData] الذي يغلف بيانات الملف [taxadmindata.json]؛
- الأسطر 14-19: يتلقى المنشئ أسماء الملفات [database.json, taxadmindata.json] كمعلمات؛
- السطر 16: تهيئة السمة [$database]؛
- السطر 18: تهيئة السمة [$taxAdminData]؛
- السطر 23: يتم تنفيذ الطريقة الوحيدة للواجهة [InterfaceDao4TransferAdminData2Database]؛
- الأسطر 26-38: يتم ملء الجداول [tbtranches, tbconstantes] على مرحلتين:
- السطر 34: يتم ملء الجدول [tbtranches] أولاً. ويتم ذلك ضمن معاملة (السطران 32 و38). تقوم الطريقة [fillTableTranches] (السطر 55) بإطلاق استثناء بمجرد حدوث خطأ ما. في هذه الحالة، يستمر التنفيذ مع جملة catch / finally في الأسطر 39-50؛
- السطر 36: يتم ملء الجدول [tbconstantes] بنفس الطريقة باستخدام الطريقة [fillTableConstantes] (السطر 60)؛
- الأسطر 39-47: الحالة التي تم فيها إلقاء استثناء بواسطة الكود؛
- الأسطر 41-44: في حالة وجود معاملة، يتم إلغاؤها؛
- السطر 46: يتم إلقاء استثناء من النوع [ExceptionImpots] مع رسالة الاستثناء الأصلي التي تكون هي نفسها من أي نوع؛
- السطور 47-50: في الجملة [finally]، يتم إغلاق الاتصال؛
فيما يلي كود الطريقة [fillTableTranches]:
private function fillTableTranches($connexion): void {
// اختصار لقاعدة البيانات
$database = $this->database;
// البيانات المراد إدخالها في قاعدة البيانات
$limites = $this->taxAdminData->getLimites();
$coeffR = $this->taxAdminData->getCoeffR();
$coeffN = $this->taxAdminData->getCoeffN();
// إفراغ الجدول في حالة وجود أي بيانات بداخله
$statement = $connexion->prepare("delete from " . $database->getTableTranches());
$statement->execute();
// إعداد عمليات الإدراج
$sqlInsert = "insert into {$database->getTableTranches()} "
. "({$database->getColLimites()}, {$database->getColCoeffR()},"
. " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
$statement = $connexion->prepare($sqlInsert);
// تنفيذ الأمر المُعد مع قيم شرائح الضرائب
for ($i = 0; $i < count($limites); $i++) {
$statement->execute([
"limites" => $limites[$i],
"coeffR" => $coeffR[$i],
"coeffN" => $coeffN[$i]]);
}
}
تعليقات
- السطر 1: تتلقى الطريقة [fillTableTranches] اتصالاً مفتوحاً كمعلمة. كما نعلم أن معاملة قد بدأت ضمن هذا الاتصال؛
- الأسطر 5-7: يتم توفير القيم المراد إدراجها في الجدول بواسطة السمة [$taxAdminData]؛
- السطور 9-10: يتم حذف المحتوى الحالي للجدول [tbtranches]؛
- الأسطر 12-15: يتم التحضير لإدراج الأسطر في الجدول. ويُستخدم هنا أسماء الأعمدة التي يوفرها السمة [$database]؛
- الأسطر 17-22: يتم تنفيذ تعليمة الإدراج التي تم إعدادها في الأسطر 12-15 عدة مرات حسب الحاجة؛
فيما يلي كود الطريقة [fillTableConstantes]:
private function fillTableConstantes($connexion): void {
// اختصار
$database = $this->database;
// يتم إفراغ الجدول تحسبًا لوجود أي بيانات بداخله
$statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
$statement->execute();
// يتم إعداد الإدراج
$taxAdminData = $this->taxAdminData;
$sqlInsert = "insert into {$database->getTableConstantes()}"
. " ({$database->getColPlafondQfDemiPart()},"
. " {$database->getColPlafondRevenusCelibatairePourReduction()},"
. " {$database->getColPlafondRevenusCouplePourReduction()},"
. " {$database->getColValeurReducDemiPart()},"
. " {$database->getColPlafondDecoteCelibataire()},"
. " {$database->getColPlafondDecoteCouple()},"
. " {$database->getColPlafondImpotCelibatairePourDecote()},"
. " {$database->getColPlafondImpotCouplePourDecote()},"
. " {$database->getColAbattementDixPourcentMax()},"
. " {$database->getColAbattementDixPourcentMin()})"
. " values ("
. ":plafondQfDemiPart,"
. ":plafondRevenusCelibatairePourReduction,"
. ":plafondRevenusCouplePourReduction,"
. ":valeurReducDemiPart,"
. ":plafondDecoteCelibataire,"
. ":plafondDecoteCouple,"
. ":plafondImpotCelibatairePourDecote,"
. ":plafondImpotCouplePourDecote,"
. ":abattementDixPourcentMax,"
. ":abattementDixPourcentMin)";
$statement = $connexion->prepare($sqlInsert);
// يتم تنفيذ الأمر المُعد
$statement->execute([
"plafondQfDemiPart" => $taxAdminData->getPlafondQfDemiPart(),
"plafondRevenusCelibatairePourReduction" => $taxAdminData->getPlafondRevenusCelibatairePourReduction(),
"plafondRevenusCouplePourReduction" => $taxAdminData->getPlafondRevenusCouplePourReduction(),
"valeurReducDemiPart" => $taxAdminData->getValeurReducDemiPart(),
"plafondDecoteCelibataire" => $taxAdminData->getPlafondDecoteCelibataire(),
"plafondDecoteCouple" => $taxAdminData->getPlafondDecoteCouple(),
"plafondImpotCelibatairePourDecote" => $taxAdminData->getPlafondImpotCelibatairePourDecote(),
"plafondImpotCouplePourDecote" => $taxAdminData->getPlafondImpotCouplePourDecote(),
"abattementDixPourcentMax" => $taxAdminData->getAbattementDixPourcentMax(),
"abattementDixPourcentMin" => $taxAdminData->getAbattementDixPourcentMin()
]);
}
تعليقات
- السطر 1: تتلقى الطريقة [fillTableConstantes] اتصالاً مفتوحاً كمعلمة. كما نعلم أن معاملة قد بدأت ضمن هذا الاتصال؛
- السطران 5-6: يتم إفراغ الجدول [tbconstantes]؛
- الأسطر 9-31: إعداد أمر الإدراج SQL. وهو أمر معقد نظرًا لوجود 10 أعمدة يجب تهيئتها في عملية الإدراج هذه، ولضرورة استرجاع أسماء الأعمدة من السمة [$database]؛
- السطور 33-44: تنفيذ أمر الإدراج. لا يوجد سوى سطر واحد يجب إدراجه. وهنا أيضًا، يصبح الكود معقدًا نظرًا لضرورة البحث عن القيم المراد إدراجها في السمة [$taxAdminData]؛
13.3.4. النص البرمجي الرئيسي


يعتمد البرنامج النصي الرئيسي على الطبقة [dao] لإجراء نقل البيانات:
<?php
// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);
// مساحة الأسماء
namespace Application;
// إدارة الأخطاء بواسطة PHP
// ini_set("display_errors", "0");
// تضمين الواجهة والفئات
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// تعريف الثوابت
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
//
try {
// إنشاء الطبقة [dao]
$dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
// نقل البيانات إلى قاعدة البيانات
$dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
// عرض الخطأ
print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// النهاية
print "Terminé\n";
exit;
تعليقات
- الأسطر 12-21: تحميل فئات وواجهات التطبيق؛
- الأسطر 24-24: الملفان jSON؛
- السطر 30: يتم إنشاء مثيل للطبقة [dao] عن طريق تمرير الملفين jSON إلى المنشئ؛
- السطر 32: يتم نقل البيانات؛
عند تنفيذ هذا الكود، نحصل على النتيجة التالية في قاعدة البيانات:

في العمود [3]، نرى القيم التي عُينت بواسطة MySQL للمفتاح الأساسي [id]. يبدأ الترقيم من 1. تم الحصول على لقطة الشاشة أعلاه بعد تنفيذ البرنامج النصي عدة مرات.


13.4. حساب الضريبة

13.4.1. البنية
كانت النسخة 04 من تطبيق حساب الضريبة تستخدم بنية متعددة الطبقات:

تنفذ الطبقة [dao] واجهة [InterfaceDao]. قمنا بإنشاء فئة تنفذ هذه الواجهة:
- [DaoImpotsWithTaxAdminDataInJsonFile] التي كانت تستخرج البيانات الضريبية من ملف jSON. كان ذلك في الإصدار 04؛
سنقوم بتنفيذ الواجهة [InterfaceDao] من خلال فئة جديدة هي [DaoImpotsWithTaxAdminDataInDatabase]، والتي ستقوم باسترداد البيانات من مصلحة الضرائب من قاعدة البيانات MySQL. ستقوم الطبقة [dao]، كما في السابق، بكتابة النتائج والأخطاء في ملفات نصية وستبحث عن بيانات دافعي الضرائب أيضًا في ملف نصي. لكن هذه المرة، ستكون هذه الملفات النصية ملفات jSON. من ناحية أخرى، نعلم أنه إذا واصلنا الالتزام بواجهة [InterfaceDao]، فلن تكون هناك حاجة لتعديل الطبقة [métier].

13.4.2. الكيان [TaxPayerData]

تُستخدم الفئة [TaxPayerData] لتغليف بيانات الملف jSON [taxpayersdata.json] التالي في فئة:
[
{
"marié": "oui",
"enfants": 2,
"salaire": 55555
},
{
"marié": "ouix",
"enfants": "2x",
"salaire": "55555x"
},
{
"marié": "oui",
"enfants": "2",
"salaire": 50000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 50000
},
{
"marié": "non",
"enfants": 2,
"salaire": 100000
},
{
"marié": "non",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 5,
"salaire": 100000
},
{
"marié": "non",
"enfants": 0,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 2,
"salaire": 30000
},
{
"marié": "non",
"enfants": 0,
"salaire": 200000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 20000
}
]
الفئة [TaxPayerData] هي كما يلي:
<?php
// مساحة الأسماء
namespace Application;
// فئة البيانات
class TaxPayerData extends BaseEntity {
// البيانات اللازمة لحساب ضريبة المكلف
protected $marié;
protected $enfants;
protected $salaire;
// نتائج حساب الضريبة
protected $impôt;
protected $surcôte;
protected $décôte;
protected $réduction;
protected $taux;
// دالات الاسترجاع والتعيين
…
}
تعليقات
- السطر 7: الفئة [TaxPayerData] تمتد من الفئة [BaseEntity]. ونظرًا لأن أساليب فئتها الأم كافية، فإن الفئة [TaxPayerData] لا تُعرّف أي أساليب بنفسها. تجدر الإشارة إلى أن سمات الفئة [TaxPayerData] مطابقة لتلك الموجودة في الملف jSON [taxpayersdata.json]؛
13.4.3. الطبقة [dao]
13.4.3.1. السمة [TraitDao]
تنفذ السمة [TraitDao] جزءًا من واجهة [InterfaceDao]. وللتذكير، فيما يلي هذه الواجهة:
<?php
// مساحة الأسماء
namespace Application;
interface InterfaceDao {
// قراءة بيانات المكلفين
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// قراءة بيانات مصلحة الضرائب (شرائح الضرائب)
public function getTaxAdminData(): TaxAdminData;
// تسجيل النتائج
public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
تقوم السمة [TraitDao] بتنفيذ الطرق [getTaxPayersData, saveResults] الخاصة بواجهة [InterfaceDao]. ونظرًا لأنه تم تغيير تعريف الكيان [TaxPayerData] بين الإصدارين 04 و05، يتعين علينا مراجعة كود [TraitDao]:
<?php
// مساحة الأسماء
namespace Application;
trait TraitDao {
// قراءة بيانات دافعي الضرائب
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
// يتم استرداد بيانات المكلفين في جدول
$baseEntity = new BaseEntity();
$baseEntity->setFromJsonFile($taxPayersFilename);
$arrayOfAttributes = $baseEntity->getArrayOfAttributes();
// جدول بيانات دافعي الضرائب
$taxPayersData = [];
// جدول الأخطاء
$errors = [];
// التكرار على جدول سمات العناصر من النوع [TaxPayerData]
$i = 0;
foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
// التحقق
$error = $this->check($attributesOfTaxPayerData);
if (!$error) {
// مكلف ضريبي بـ +
$taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
} else {
// خطأ في + - يتم تدوين رقم البيانات غير الصالحة
$error = ["numéro" => $i] + $error;
$errors[] = $error;
}
// التالي
$i++;
}
// يتم حفظ الأخطاء في ملف json
$string = "";
foreach ($errors as $error) {
$string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
}
$this->saveString($errorsFilename, $string);
// نتيجة الدالة
return $taxPayersData;
}
private function check(array $attributesOfTaxPayerData): array {
// يتم التحقق من بيانات [$taxPayerData]
// قائمة السمات التي تحتوي على أخطاء
$attributes = [];
// يجب أن يكون الحالة الاجتماعية نعم أو لا
$marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
$erreur = ($marié !== "oui" and $marié !== "non");
if ($erreur) {
// يُلاحظ وجود خطأ
$attributes[] = ["marié" => $marié];
}
// يجب أن يكون عدد الأطفال عددًا صحيحًا موجبًا أو صفرًا
$enfants = trim($attributesOfTaxPayerData["enfants"]);
if (!preg_match("/^\d+$/", $enfants)) {
// تم تسجيل الخطأ
$erreur = TRUE;
$attributes[] = ["enfants" => $enfants];
} else {
$enfants = (int) $enfants;
}
// يجب أن يكون الراتب عددًا صحيحًا موجبًا أو صفرًا (بدون سنتات اليورو)
$salaire = trim($attributesOfTaxPayerData["salaire"]);
if (!preg_match("/^\d+$/", $salaire)) {
// تم تسجيل الخطأ
$erreur = TRUE;
$attributes[] = ["salaire" => $salaire];
} else {
$salaire = (int) $salaire;
}
// خطأ؟
if ($erreur) {
// العودة مع وجود خطأ
return ["erreurs" => $attributes];
} else {
// العودة بدون خطأ
return [];
}
}
// تسجيل النتائج
public function saveResults(string $resultsFilename, array $taxPayersData): void {
// تسجيل الجدول [$taxPayersData] في الملف النصي [$resultsFileName]
// إذا لم يكن الملف النصي [$resultsFileName] موجودًا، فسيتم إنشاؤه
// بناء السلسلة jSON من النتائج
$string = "[" . implode(",
", $taxPayersData) . "]";
// تسجيل هذه السلسلة
$this->saveString($resultsFilename, $string);
}
// تسجيل نتائج جدول في ملف نصي
private function saveString(string $fileName, string $data): void {
// تسجيل السلسلة [$data] في الملف النصي [$fileName]
// إذا لم يكن الملف النصي [$fileName] موجودًا، يتم إنشاؤه
if (file_put_contents($fileName, $data) === FALSE) {
throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
}
}
}
تعليقات
- تقوم [TraitDao] بتنفيذ الطريقتين [getTaxPayersData] (السطر 9) و [saveResults] (السطر 86) من الواجهة [InterfaceDao]؛
- السطر 9: تتلقى الطريقة [getTaxPayersData] المعلمات التالية:
- [$taxPayersFilename]: اسم ملف jSON الخاص ببيانات دافعي الضرائب [taxpayersdata.json]؛
- [$errorsFilename]: اسم ملف jSON الخاص بالأخطاء [errors.json]؛
- السطور 11-13: يتم نقل محتوى ملف jSON الخاص ببيانات دافعي الضرائب إلى جدول ترابطي [$arrayOfAttributes]. إذا تبين أن الملف jSON غير قابل للاستخدام، يتم إطلاق استثناء [ExceptionImpots]؛
- السطر 15: سيحتوي الجدول [$taxPayersData] على بيانات دافعي الضرائب المُغلفة في كائنات من النوع [TaxPayerData]؛
- السطر 17: سيتم تجميع الأخطاء في الجدول [$errors]؛
- الأسطر 99-33: إنشاء الجدول [$taxPayersData]؛
- السطر 22: قبل تغليف البيانات في نوع [TaxPayerData]، يتم التحقق منها. تُرجع الطريقة [check]:
- مصفوفة [‘erreurs’=>[…]] مع السمات الخاطئة إذا كانت البيانات غير صحيحة؛
- مصفوفة فارغة إذا كانت البيانات صحيحة؛
- السطر 25: الحالة التي تكون فيها البيانات صالحة. يتم إنشاء كائن جديد [TaxPayerData] وإضافته إلى المصفوفة [$taxPayersData]؛
- الأسطر 26-30: الحالة التي تكون فيها البيانات غير صالحة. يُشار في الخطأ إلى رقم الكائن الخاطئ [TaxPayerData] الموجود في الملف jSON حتى يتمكن المستخدم من العثور عليه، ثم يُضاف الخطأ إلى المصفوفة [$errors]؛
- الأسطر 35-39: يتم تسجيل الأخطاء التي تمت مواجهتها في الملف jSON [$errorsFilename] الذي تم تمريره كمعلمة، السطر 9؛
- السطر 41: يتم إرجاع المصفوفة التي تحتوي على الكائنات [TaxPayerData] التي تم إنشاؤها: كان هذا هو الهدف من الأسلوب؛
- الأسطر 44-83: تتحقق الطريقة الخاصة [check] من صحة المعلمات [marié, enfants, salaire] للمصفوفة [$attributesOfTaxPayerData] التي تم تمريرها كمعلمة في السطر 44. في حالة وجود سمات خاطئة، تقوم بتجميعها في المصفوفة [$attributes] (الأسطر 47، 53، 60، 70) في شكل مصفوفة [‘attribut erroné’=> valeur de l’attribut erroné]؛
- السطر 78: في حالة وجود أخطاء، يتم إرجاع مصفوفة [‘erreurs’=>$attributes]؛
- السطر 81: إذا لم تكن هناك أخطاء، يتم إرجاع مصفوفة أخطاء فارغة؛
- الأسطر 86-93: تنفيذ الطريقة [saveResults] الخاصة بالواجهة [InterfaceDao]؛
- السطر 90: يتم إنشاء السلسلة jSON لتسجيلها في الملف jSON [$resultsFilename] الذي تم تمريره كمعلمة في السطر 86. يجب إنشاء السلسلة jSON من مصفوفة:
- يُفصل كل عنصر في المصفوفة عن العنصر التالي بفاصلة وخط جديد؛
- المصفوفة بأكملها محاطة بأقواس معقوفة []؛
- السطر 92: يتم تسجيل السلسلة jSON في الملف jSON [$resultsFilename]؛
13.4.3.2. الفئة [DaoImpotsWithTaxAdminDataInDatabase]
تنفذ الفئة [DaoImpotsWithTaxAdminDataInDatabase] الواجهة [InterfaceDao] بالطريقة التالية:
<?php
// مساحة الأسماء
namespace Application;
// تعريف فئة ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
// استخدام سمة
use TraitDao;
// الكائن من النوع TaxAdminData الذي يحتوي على بيانات شرائح الضرائب
private $taxAdminData;
// الكائن من النوع [Database] الذي يحتوي على خصائص BD
private $database;
// المُنشئ
public function __construct(string $databaseFilename) {
// يتم حفظ تكوين قاعدة البيانات jSON
$this->database = (new Database())->setFromJsonFile($databaseFilename);
// يتم إعداد السمة
$this->taxAdminData = new TaxAdminData();
try {
// يتم فتح الاتصال بقاعدة البيانات
$connexion = new \PDO(
$this->database->getDsn(),
$this->database->getId(),
$this->database->getPwd());
// نريد أن يتم إلقاء استثناء عند حدوث أي خطأ في SGBD
$connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// يتم بدء معاملة
$connexion->beginTransaction();
// يتم ملء جدول شرائح الضرائب
$this->getTranches($connexion);
// يتم ملء جدول الثوابت
$this->getConstantes($connexion);
// يتم إنهاء المعاملة بنجاح
$connexion->commit();
} catch (\PDOException $ex) {
// هل هناك معاملة جارية؟
if (isset($connexion) && $connexion->inTransaction()) {
// إنهاء المعاملة بعد الفشل
$connexion->rollBack();
}
// إعادة الاستثناء إلى الكود المستدعي
throw new ExceptionImpots($ex->getMessage());
} finally {
// يتم إغلاق الاتصال
$connexion = NULL;
}
}
// قراءة البيانات من قاعدة البيانات
private function getTranches($connexion): void {
…
}
// قراءة جدول الثوابت
private function getConstantes($connexion): void {
…
}
// إرجاع البيانات التي تسمح بحساب الضريبة
public function getTaxAdminData(): TaxAdminData {
return $this->taxAdminData;
}
}
تعليقات
- السطر 4: يتم الاحتفاظ بمساحة الأسماء المستخدمة بالفعل لتنفيذات أخرى للطبقة [dao]؛
- السطر 7: تُنفذ الفئة [DaoImpotsWithTaxAdminDataInDatabase] الواجهة [InterfaceDao]؛
- السطر 9: يتم استيراد السمة [TraitDao]. ومن المعروف أن هذه السمة تنفذ جزءًا من الواجهة. الطريقة الوحيدة المتبقية التي يتعين تنفيذها هي الطريقة [getTaxAdminData] في الأسطر 62-64. تقتصر هذه الطريقة على إرجاع السمة الخاصة [taxAdminData] الموجودة في السطر 11. ونستنتج من ذلك أن المنشئ سيتعين عليه تهيئة هذه السمة. وهذا هو دوره الوحيد؛
- السطر 16: يتلقى المنشئ كمعلمة وحيدة [$databaseFilename]، وهو اسم الملف jSON [database.json] الذي يحدد قاعدة البيانات MySQL [dbimpots-2019] ؛
- السطر 18: يُستخدم الملف jSON [$databaseFilename] لإنشاء كائن من النوع [Database]، والذي تم إنشاؤه وتخزينه في السمة [$database] في السطر 13. إذا تعذر استخدام الملف jSON بشكل صحيح، تم إطلاق استثناء [ExceptionImpots]؛
- السطر 20: يتم إنشاء الكائن [$this→taxAdminData] الذي يجب على المنشئ تهيئته؛
- الأسطر 22-26: يتم فتح الاتصال بقاعدة البيانات. لاحظ استخدام الترميز [\PDO] للإشارة إلى الفئة [PDO] التابعة لـ PHP. ففي الواقع، نظرًا لوجودنا في مساحة الأسماء [Application]، لو كتبنا ببساطة [PDO]، فسيتم إضافة مساحة الأسماء الحالية كبادئة لهذا الاسم النسبي، مما يؤدي إلى الحصول على الفئة [Application\PDO] التي لا وجود لها؛
- السطر 28: عند حدوث خطأ، سيقوم SGBD بتشغيل \PDOException (السطر 37)؛
- السطر 30: يتم بدء معاملة. هذه المعاملة ليست مفيدة حقًّا لأن الأمرين SQL فقط هما اللذان سيتم تنفيذهما، وهما أمران لا يُحدثان أي تغيير في قاعدة البيانات. ومع ذلك، يتم القيام بذلك لعزل العملية عن مستخدمي قاعدة البيانات الآخرين؛
- السطر 32: تتم قراءة جدول شرائح الضرائب [tbtranches] بواسطة الطريقة الخاصة [getTranches] الموجودة في السطر 52؛
- السطر 34: تتم قراءة جدول ثوابت الحساب [tbconstantes] بواسطة الطريقة الخاصة [getConstantes] الموجودة في السطر 57؛
- السطر 36: إذا وصلنا إلى هذا السطر، فهذا يعني أن كل شيء سار على ما يرام. وبالتالي، يتم إقرار المعاملة؛
- الأسطر 37-42: إذا وصلنا إلى هنا، فهذا يعني أن استثناءً قد حدث. لذلك يتم إلغاء المعاملة إن كانت هناك معاملة جارية (الأسطر 39-42). السطر 44، للحصول على استثناءات متجانسة، يتم إعادة إرسال رسالة الاستثناء المستلمة، هذه المرة على شكل استثناء من النوع [ExceptionImpots]؛
- الأسطر 45-48: في جميع الحالات (سواء حدث استثناء أم لا) يتم إغلاق الاتصال؛
الطريقة [getTranches] هي كما يلي:
private function getTranches($connexion): void {
// الاختصارات
$database = $this->database;
$taxAdminData = $this->taxAdminData;
// يتم إعداد الاستعلام SELECT
$statement = $connexion->prepare(
"select {$database->getColLimites()}," .
" {$database->getColCoeffR()}," .
" {$database->getColCoeffN()}" .
" from {$database->getTableTranches()}");
// تنفيذ الأمر المُعد مع قيم شرائح الضرائب
$statement->execute();
// استخلاص النتيجة
$limites = [];
$coeffR = [];
$coeffN = [];
// ملء الجداول الثلاثة
while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
$limites[] = (float) $tranche->{$database->getColLimites()};
$coeffR[] = (float) $tranche->{$database->getColCoeffR()};
$coeffN[] = (float) $tranche->{$database->getColCoeffN()};
}
// يتم تخزين البيانات في السمة [$taxAdminData] للفئة
$taxAdminData->setFromArrayOfAttributes([
"limites" => $limites,
"coeffR" => $coeffR,
"coeffN" => $coeffN
]);
}
تعليقات
- السطر 1: تتلقى الطريقة كمعلمة [$connexion]، وهي اتصال مفتوح تجري فيه معاملة حالية؛
- الأسطر 2-4: يتم إنشاء اختصارين لتجنب الحاجة إلى كتابة [$this->database] و [$taxAdminData = $this->taxAdminData] في جميع أنحاء الكود. ما لدينا هنا هو نسخ من مراجع الكائنات وليس نسخًا من الكائنات نفسها؛
- الأسطر 6-10: يتم إعداد الأمر SELECT، ثم تنفيذه في السطر 12؛
- الأسطر 13-22: يتم استغلال نتيجة SELECT. يتم تجميع المعلومات المستلمة في ثلاثة مصفوفات [limites, coeffR, coeffN]؛
- الأسطر 24-28: تُستخدم الجداول الثلاثة لتهيئة السمة [$this->taxAdminData] للفئة؛
الطريقة الخاصة [getConstantes] هي كما يلي:
private function getConstantes($connexion): void {
// الاختصارات
$database = $this->database;
$taxAdminData = $this->taxAdminData;
// يتم إعداد الاستعلام SELECT
$select = "select {$database->getColPlafondQfDemiPart()}," .
"{$database->getColPlafondRevenusCelibatairePourReduction()}," .
"{$database->getColPlafondRevenusCouplePourReduction()}," . "{$database->getColValeurReducDemiPart()}," .
"{$database->getColPlafondDecoteCelibataire()}," . "{$database->getColPlafondDecoteCouple()}," .
"{$database->getColPlafondImpotCelibatairePourDecote()}," . "{$database->getColPlafondImpotCouplePourDecote()}," .
"{$database->getColAbattementDixPourcentMax()}," . "{$database->getColAbattementDixPourcentMin()}" .
" from {$database->getTableConstantes()}";
$statement = $connexion->prepare($select);
// يتم تنفيذ الأمر المُعد
$statement->execute();
// يتم استخراج النتيجة - سطر واحد فقط هنا
$row = $statement->fetch(\PDO::FETCH_OBJ);
// يتم تهيئة السمة [$taxAdminData]
$taxAdminData->setPlafondQfDemiPart($row->{$database->getColPlafondQfDemiPart()});
$taxAdminData->setPlafondRevenusCelibatairePourReduction(
$row->{$database->getColPlafondRevenusCelibatairePourReduction()});
$taxAdminData->setPlafondRevenusCouplePourReduction($row->{$database->getColPlafondRevenusCouplePourReduction()});
$taxAdminData->setValeurReducDemiPart($row->{$database->getColValeurReducDemiPart()});
$taxAdminData->setPlafondDecoteCelibataire($row->{$database->getColPlafondDecoteCelibataire()});
$taxAdminData->setPlafondDecoteCouple($row->{$database->getColPlafondDecoteCouple()});
$taxAdminData->setPlafondImpotCelibatairePourDecote($row->{$database->getColPlafondImpotCelibatairePourDecote()});
$taxAdminData->setPlafondImpotCouplePourDecote($row->{$database->getColPlafondImpotCouplePourDecote()});
$taxAdminData->setAbattementDixPourcentMax($row->{$database->getColAbattementDixPourcentMax()});
$taxAdminData->setAbattementDixPourcentMin($row->{$database->getColAbattementDixPourcentMin()});
}
تعليقات
- السطر 1: تتلقى الطريقة كمعلمة [$connexion]، وهي اتصال مفتوح تجري فيه معاملة حالية؛
- الأسطر 2-4: يتم إنشاء اختصارين لتجنب الحاجة إلى كتابة [$this->database] و [$taxAdminData = $this->taxAdminData] في جميع أنحاء الكود. ما لدينا هنا هو نسخ من مراجع الكائنات وليس نسخًا من الكائنات نفسها؛
- الأسطر 6-15: يتم إعداد الأمر SELECT، ثم تنفيذه في السطر 15؛
- الأسطر 17-29: يتم استغلال نتيجة SELECT. تُستخدم المعلومات المسترجعة لتهيئة السمة [$this->taxAdminData] للفئة؛
ملاحظة: تجدر الإشارة إلى أن الفئة لا تعتمد على SGBD وMySQL. فالرمز المستدعي هو الذي يحدد SGBD المستخدم عبر DSN من قاعدة البيانات.
13.4.4. الطبقة [métier]

- لقد قمنا للتو بتنفيذ الطبقة [dao] (3)؛
- ولأننا التزمنا بواجهة [InterfaceDao]، فإن الطبقة [métier] (2) يمكن نظريًا أن تظل دون تغيير. ومع ذلك، لم نكتفِ بتعديل الطبقة [dao] فحسب. بل قمنا أيضًا بتعديل الكيانات المشتركة بين جميع الطبقات؛
تنفذ الطبقة [métier] الواجهة [InterfaceMetier] التالية:
<?php
// مساحة الأسماء
namespace Application;
interface InterfaceMetier {
// حساب ضرائب أحد دافعي الضرائب
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// حساب الضرائب في الوضع الدفعي
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
- السطر 12: تستخدم الطريقة [executeBatchImpots] الآن الملف jSON [$taxPayersFileName] بينما في الإصدار 04، كان ملفًا نصيًا أساسيًا.
في الإصدار 04، كانت الطريقة [executeBatchImpots] كما يلي:
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// السماح بترحيل الاستثناءات الواردة من الطبقة [dao]
// استرداد بيانات المكلفين
$taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// جدول النتائج
$results = [];
// يتم استغلالها
foreach ($taxPayersData as $taxPayerData) {
// يتم حساب الضريبة
$result = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
// يتم استكمال [$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"]);
// يتم إدراج النتيجة في جدول النتائج
$results [] = $taxPayerData;
}
// تسجيل النتائج
$this->dao->saveResults($resultsFileName, $results);
}
- أصبح السطر 15 الآن غير صحيح. في التعريف الجديد للفئة [TaxPayerData]، لم تعد الطريقة [setMontant] موجودة؛
في الإصدار 05، ستكون الطريقة [executeBatchImpots] كما يلي:
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// يتم السماح بإظهار الاستثناءات التي تأتي من الطبقة [dao]
// يتم استرداد البيانات الضريبية
$taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// جدول النتائج
$results = [];
// يتم تحليلها
foreach ($taxPayersData as $taxPayerData) {
// يتم حساب الضريبة
$result = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
// يتم استكمال [$taxPayerData]
$taxPayerData->setFromArrayOfAttributes($result);
// يتم إدراج النتيجة في جدول النتائج
$results [] = $taxPayerData;
}
// تسجيل النتائج
$this->dao->saveResults($resultsFileName, $results);
}
تعليقات
- السطر 15: بدلاً من استخدام مُعيِّنات القيم الفردية للفئة [TaxPayerData]، يتم استخدام مُعيِّن القيمة الشامل الخاص بها [setFromArrayOfAttributes]؛
- لا يلزم تعديل بقية الكود؛
13.4.5. النص البرمجي الرئيسي

- لقد قمنا للتو بتنفيذ الطبقات [dao] (3) و [métier] (2)؛
- ويبقى لنا كتابة البرنامج النصي الرئيسي (1)؛
النص البرمجي الرئيسي مشابه لذلك الموجود في الإصدار 04:
<?php
// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);
// مساحة الأسماء
namespace Application;
// إدارة الأخطاء بواسطة PHP
//ini_set("display_errors", "0");
// تضمين الواجهة والفئات
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../Métier/InterfaceMetier.php";
require_once __DIR__ . "/../Métier/Metier.php";
//
// تعريف الثوابت
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";
try {
// إنشاء الطبقة [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// إنشاء الطبقة [métier]
$métier = new Metier($dao);
// حساب الضرائب في الوضع الدفعي
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// عرض الخطأ
print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// النهاية
print "Terminé\n";
exit;
تعليقات
- الأسطر 12-22: تحميل جميع ملفات الإصدار 05؛
- الأسطر 25-29: أسماء الملفات المختلفة jSON الخاصة بالتطبيق؛
- السطر 33: إنشاء الطبقة [dao]؛
- السطر 35: إنشاء الطبقة [métier]؛
- السطر 37: استدعاء الأسلوب [executeBatchImpots] من الطبقة [métier]؛
النتائج
ينتج التطبيق ملفين jSON:
- [resultats.json]: نتائج الحسابات المختلفة للضرائب؛
- [errors.json]: الذي يشير إلى الأخطاء الموجودة في الملف jSON و[taxpayersdata.json]؛
الملف [errors.json] هو التالي:
{
"numéro": 1,
"erreurs": [
{
"marié": "ouix"
},
{
"enfants": "2x"
},
{
"salaire": "55555x"
}
]
}
وهذا يعني أن العنصر رقم 1 في جدول دافعي الضرائب في الملف [taxpayersdata.json] غير صحيح. وكان الملف [taxpayersdata.json] كما يلي:
[
{
"marié": "oui",
"enfants": 2,
"salaire": 55555
},
{
"marié": "ouix",
"enfants": "2x",
"salaire": "55555x"
},
{
"marié": "oui",
"enfants": "2",
"salaire": 50000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 50000
},
{
"marié": "non",
"enfants": 2,
"salaire": 100000
},
{
"marié": "non",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 5,
"salaire": 100000
},
{
"marié": "non",
"enfants": 0,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 2,
"salaire": 30000
},
{
"marié": "non",
"enfants": 0,
"salaire": 200000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 20000
}
]
أما ملف النتائج [resultats.json] فهو كما يلي:
[
{
"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é": "non",
"enfants": 3,
"salaire": 100000,
"impôt": 16782,
"surcôte": 7176,
"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é": "oui",
"enfants": 5,
"salaire": 100000,
"impôt": 4230,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
},
{
"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": 20000,
"impôt": 0,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0
}
]
تتوافق هذه النتائج مع تلك الموجودة في الإصدار 04.
13.5. اختبارات [Codeception]
كما تم في الفقرة الخاصة بالإصدار 04، سنقوم بكتابة اختبارات [Codeception] للإصدار 05.

13.5.1. اختبار الطبقة [dao]
اختبار [DaoTest.php] هو كما يلي:
<?php
// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);
// مساحة الأسماء
namespace Application;
// المجلدات الجذرية
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// تضمين الواجهة والفئات
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// مكتبات الجهات الخارجية
require_once VENDOR . "/autoload.php";
// تعريف الثوابت
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// إنشاء الطبقة [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// الاختبارات
public function testTaxAdminData() {
// ثوابت الحساب
$this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());
…
}
}
تعليقات
- الأسطر 9-33: تعريف بيئة الاختبار. نستخدم نفس البيئة المستخدمة في البرنامج النصي الرئيسي [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] الموصوف في الفقرة الرابط؛
- الأسطر 39-44: إنشاء الطبقة [dao]؛
- السطر 43: السمة [$this→taxAdminData] تحتوي على البيانات المراد اختبارها؛
- الأسطر 47-51: الطريقة [testTaxAdminData] هي تلك الموضحة في الفقرة «الرابط»؛
نتائج الاختبار هي كما يلي:

13.5.2. اختبار الطبقة [métier]
اختبار [MetierTest.php] هو كما يلي:
<?php
// الالتزام الصارم بأنواع المعلمات المعلنة للدوال
declare (strict_types=1);
// مساحة الأسماء
namespace Application;
// المجلدات الجذرية
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// تضمين الواجهة والفئات
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// المكتبات الخارجية
require_once VENDOR . "/autoload.php";
// تعريف الثوابت
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";
class MetierTest extends \Codeception\Test\Unit {
// طبقة الأعمال
private $métier;
public function __construct() {
parent::__construct();
// إنشاء الطبقة [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// إنشاء الطبقة [métier]
$this->métier = new Metier($dao);
}
// اختبارات
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 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"]);
}
}
تعليقات
- الأسطر 9-33: تعريف بيئة الاختبار. نستخدم نفس البيئة المستخدمة في البرنامج النصي الرئيسي [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] الموصوف في الفقرة المرتبطة؛
- الأسطر 39-45: إنشاء الطبقتين [dao] و [métier]؛
- السطر 44: يشير السمة [$this→métier] إلى الطبقة [métier]؛
- الأسطر 47-64: الطرق [test1, test2…, test11] هي تلك الموضحة في الفقرة [رابط]؛
نتائج الاختبار هي كما يلي:
