13. تمرين عملي – الإصدار 5

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

تنفذ طبقة [dao] واجهة [InterfaceDao]. قمنا بإنشاء فئة تنفذ هذه الواجهة:
- [DaoImportsWithTaxAdminDataInJsonFile]، التي تسترد بيانات الضرائب من ملف JSON؛
سنقوم بتنفيذ واجهة [InterfaceDao] من خلال فئة جديدة [DaoImportsWithTaxAdminDataInDatabase] ستقوم باسترداد بيانات إدارة الضرائب من قاعدة بيانات 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] بواسطة نظام إدارة قواعد البيانات (DBMS). وهذا يعني أن MySQL ستدير قيم المفتاح الأساسي بنفسها أثناء عمليات الإدراج. وسيخصص القيمة 1 للمفتاح الأساسي للإدراج الأول، ثم 2 للإدراج التالي، وهكذا دواليك؛
- في [7]، يقدم المعالج خيارات تكوين إضافية للمفتاح الأساسي. هنا، نقبل ببساطة [7] القيم الافتراضية؛

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

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

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

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



الخيار [11] يُنشئ ملف SQL التالي [dbimpots-2019.sql]:
-- phpMyAdmin SQL Dump
-- version 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generation Time: Jun 30, 2019 at 01:10 PM
-- Server version: 5.7.24
-- PHP Version: 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 */;
--
-- Database: `dbimpots-2019`
--
CREATE DATABASE IF NOT EXISTS `dbimpots-2019` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `dbimpots-2019`;
-- --------------------------------------------------------
--
-- Table structure for table `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;
-- --------------------------------------------------------
--
-- Table structure for table `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;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbconstantes`
--
ALTER TABLE `tbconstantes`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `tbtranches`
--
ALTER TABLE `tbtranches`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbconstantes`
--
ALTER TABLE `tbconstantes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `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]، واجهات وفئات طبقة [business]؛
- في [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 {
// attribute
protected $arrayOfAttributes;
// initialization from a jSON file
public function setFromJsonFile(string $jsonFilename) {
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($jsonFilename);
$erreur = FALSE;
// mistake?
if (!$fileContents) {
// we note the error
$erreur = TRUE;
$message = "Le fichier des données [$jsonFilename] n'existe pas";
}
if (!$erreur) {
// retrieve the jSON code from the configuration file in an associative array
$this->arrayOfAttributes = \json_decode($fileContents, true);
// mistake?
if ($this->arrayOfAttributes === FALSE) {
// we note the error
$erreur = TRUE;
$message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
}
}
// mistake?
if ($erreur) {
// throw an exception
throw new ExceptionImpots($message);
}
// initialization of class attributes
foreach ($this->arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// we return the object
return $this;
}
public function checkForAllAttributes() {
// check that all keys have been initialized
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) {
// initialize certain attributes of the
foreach ($arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// object is returned
return $this;
}
// toString
public function __toString() {
// object attributes
$arrayOfAttributes = \get_object_vars($this);
// remove parent class attribute
unset($arrayOfAttributes["arrayOfAttributes"]);
// object's Json string
return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
}
// getter
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 {
// attributes
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 | اسم الجدول الذي يحتوي على شرائح الضرائب |
colLimits colRate colCoeffN | أسماء الأعمدة في الجدول [tableTranches] |
tableConstants | اسم الجدول الذي يحتوي على ثوابت حساب الضرائب |
colIncomeCeilingForHalfShare حد الدخل الفردي للتخفيض حد الدخل للزوجين للتخفيض colHalfShareReductionValue حد الائتمان الضريبي للأفراد الحد الأقصى لخصم الزوجين الحد الأقصى للدخل الفردي للخصم العمود الحد الأقصى للضريبة للزوجين من أجل الخصم colالحد_الأقصى_لخصم_الـ10_بالمائة colالحد الأدنى لخصم العشرة بالمائة | أسماء الأعمدة في الجدول [tableConstants] الذي يحتوي على ثوابت حساب الضرائب |
لماذا نسمي الجداول والأعمدة بينما نعرف أسمائها بالفعل ومن غير المرجح أن تتغير؟ بعد نظام إدارة قواعد البيانات MySQL، سنستخدم نظام إدارة قواعد البيانات PostgreSQL لتخزين بيانات إدارة الضرائب. ومع ذلك، لا تتبع أسماء الأعمدة والجداول في Postgres نفس القواعد المتبعة في MySQL. سنضطر إلى استخدام أسماء مختلفة. وينطبق هذا أيضًا على أنظمة إدارة قواعد البيانات الأخرى. إذا أردنا كودًا قابلاً للنقل عبر أنظمة إدارة قواعد البيانات، فمن الأفضل استخدام المعلمات بدلاً من أسماء الجداول والأعمدة المبرمجة بشكل ثابت.
لنعد إلى كود فئة [Database]:
<?php
namespace Application;
class Database extends BaseEntity {
// attributes
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;
// setter
// initialization
public function setFromJsonFile(string $jsonFilename) {
// parent
parent::setFromJsonFile($jsonFilename);
// check that all attributes have been initialized
parent::checkForAllAttributes();
// object is returned
return $this;
}
// getters and setters
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 وما بعده: وظائف الحصول والتعيين لسمات الفئة؛
13.3.2.3. كيان [TaxAdminData]
الكيان [TaxAdminData] هو كما يلي:
<?php
namespace Application;
class TaxAdminData extends BaseEntity {
// tax brackets
protected $limites;
protected $coeffR;
protected $coeffN;
// tax calculation constants
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 {
// tax brackets
protected $limites;
protected $coeffR;
protected $coeffN;
// tax calculation constants
protected $plafondQfDemiPart;
protected $plafondRevenusCelibatairePourReduction;
protected $plafondRevenusCouplePourReduction;
protected $valeurReducDemiPart;
protected $plafondDecoteCelibataire;
protected $plafondDecoteCouple;
protected $plafondImpotCouplePourDecote;
protected $plafondImpotCelibatairePourDecote;
protected $abattementDixPourcentMax;
protected $abattementDixPourcentMin;
// initialization
public function setFromJsonFile(string $taxAdminDataFilename) {
// parent
parent::setFromJsonFile($taxAdminDataFilename);
// check that all attributes have been initialized
parent::checkForAllAttributes();
// check that attribute values are real >=0
foreach ($this as $key => $value) {
if ($key !== "arrayOfAttributes") {
// $value must be a real number >=0 or an array of reals >=0
$result = $this->check($value);
// mistake?
if ($result->erreur) {
// throw an exception
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// we note the value
$this->$key = $result->value;
}
}
}
// we return the object
return $this;
}
protected function check($value): \stdClass {
// $value is an array of string elements or a single element
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// transform the array of strings into an array of reals
$newTableau = [];
$result = new \stdClass();
// table elements must be positive or zero decimal numbers
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// put the float in newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// we note the error
$result->erreur = TRUE;
// we leave
return $result;
}
}
// we return the result
$result->erreur = FALSE;
if (!\is_array($value)) {
// a single value
$result->value = $newTableau[0];
} else {
// a list of values
$result->value = $newTableau;
}
return $result;
}
// getters and setters
…
}
تعليقات
- السطر 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
namespace Application;
interface InterfaceDao4TransferAdminData2Database {
public function transferAdminData2Database(): void;
}
تعليقات
- السطر 8: الطريقة [transferAdminData2Database] مسؤولة عن تخزين بيانات الإدارة الضريبية في قاعدة بيانات؛
سيتم تنفيذ واجهة [InterfaceDao4TransferAdminData2Database] بواسطة الفئة [DaoTransferAdminDataFromJsonFile2Database] التالية:
<?php
// namespace
namespace Application;
// definition of a TransferAdminDataFromFile2DatabaseDao class
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
// target database attributes
private $database;
// tax administration data
private $taxAdminData;
// manufacturer
public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
// save configuration
$this->database = (new Database())->setFromJsonFile($databaseFilename);
// tax data is stored
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
}
// transfers tax bracket data from a text file
// to database
public function transferAdminData2Database(): void {
// we work on the basis
$database = $this->database;
try {
// open the database connection
$connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
// we want every SGBD error to trigger an exception
$connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// start a transaction
$connexion->beginTransaction();
// fill in the tax bracket table
$this->fillTableTranches($connexion);
// fill in the constants table
$this->fillTableConstantes($connexion);
// the transaction is completed successfully
$connexion->commit();
} catch (\PDOException $ex) {
// is there a transaction in progress?
if (isset($connexion) && $connexion->inTransaction()) {
// transaction ends in failure
$connexion->rollBack();
}
// trace the exception back to the calling code
throw new ExceptionImpots($ex->getMessage());
} finally {
// close the connection
$connexion = NULL;
}
}
// filling the tax bracket table
private function fillTableTranches($connexion): void {
…
}
// filling the constants table
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 {
// raccourci pour la bd
$database = $this->database;
// les données à insérer dans la base de données
$limites = $this->taxAdminData->getLimites();
$coeffR = $this->taxAdminData->getCoeffR();
$coeffN = $this->taxAdminData->getCoeffN();
// on vide la table au cas où il y aurait qq chose dedans
$statement = $connexion->prepare("delete from " . $database->getTableTranches());
$statement->execute();
// on prépare les insertions
$sqlInsert = "insert into {$database->getTableTranches()} "
. "({$database->getColLimites()}, {$database->getColCoeffR()},"
. " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
$statement = $connexion->prepare($sqlInsert);
// on exécute l'ordre préparé avec les valeurs des tranches d'impôts
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 {
// raccourci
$database = $this->database;
// on vide la table au cas où il y aurait qq chose dedans
$statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
$statement->execute();
// on prépare l'insertion
$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);
// on exécute l'ordre préparé
$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
// 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__ . "/../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";
//
// definition of constants
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
//
try {
// creation of the [dao] layer
$dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
// data transfer to the database
$dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
// error is displayed
print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
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]، فلن تكون هناك حاجة لتعديل طبقة [business].

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
namespace Application;
// data class
class TaxPayerData extends BaseEntity {
// data required to calculate the taxpayer's tax liability
protected $marié;
protected $enfants;
protected $salaire;
// tax calculation results
protected $impôt;
protected $surcôte;
protected $décôte;
protected $réduction;
protected $taux;
// getters and setters
…
}
تعليقات
- السطر 7: تمتد فئة [TaxPayerData] من فئة [BaseEntity]. ونظرًا لأن أساليب فئتها الأم كافية، فإن فئة [TaxPayerData] لا تحدد أيًا من أساليبها الخاصة. لاحظ أن سمات فئة [TaxPayerData] مطابقة لتلك الموجودة في ملف JSON [taxpayersdata.json]؛
13.4.3. طبقة [dao]
13.4.3.1. السمة [TraitDao]
تنفذ السمة [TraitDao] جزءًا من واجهة [InterfaceDao]. دعونا نراجعها:
<?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;
}
تنفذ السمة [TraitDao] طريقتي [getTaxPayersData] و [saveResults] للواجهة [InterfaceDao]. ونظرًا لتغيير تعريف الكيان [TaxPayerData] بين الإصدارين 04 و 05، فإننا بحاجة إلى تحديث الكود في [TraitDao]:
<?php
// namespace
namespace Application;
trait TraitDao {
// reading taxpayer data
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
// retrieve taxpayer data in a table
$baseEntity = new BaseEntity();
$baseEntity->setFromJsonFile($taxPayersFilename);
$arrayOfAttributes = $baseEntity->getArrayOfAttributes();
// taxpayer data table
$taxPayersData = [];
// error table
$errors = [];
// we loop over the array of attributes of elements of type [TaxPayerData]
$i = 0;
foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
// check
$error = $this->check($attributesOfTaxPayerData);
if (!$error) {
// one more taxpayer
$taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
} else {
// an error of + - the invalid data number is noted
$error = ["numéro" => $i] + $error;
$errors[] = $error;
}
// following
$i++;
}
// save errors in a json file
$string = "";
foreach ($errors as $error) {
$string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
}
$this->saveString($errorsFilename, $string);
// function result
return $taxPayersData;
}
private function check(array $attributesOfTaxPayerData): array {
// check the data in [$taxPayerData]
// the list of erroneous attributes
$attributes = [];
// marital status must be yes or no
$marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
$erreur = ($marié !== "oui" and $marié !== "non");
if ($erreur) {
// we note the error
$attributes[] = ["marié" => $marié];
}
// the number of children must be a positive integer or zero
$enfants = trim($attributesOfTaxPayerData["enfants"]);
if (!preg_match("/^\d+$/", $enfants)) {
// we note the error
$erreur = TRUE;
$attributes[] = ["enfants" => $enfants];
} else {
$enfants = (int) $enfants;
}
// the salary must be a positive integer or zero (without euro cents)
$salaire = trim($attributesOfTaxPayerData["salaire"]);
if (!preg_match("/^\d+$/", $salaire)) {
// we note the error
$erreur = TRUE;
$attributes[] = ["salaire" => $salaire];
} else {
$salaire = (int) $salaire;
}
// mistake?
if ($erreur) {
// return with error
return ["erreurs" => $attributes];
} else {
// error-free return
return [];
}
}
// 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
// construction of the jSON results chain
$string = "[" . implode(",
", $taxPayersData) . "]";
// recording this channel
$this->saveString($resultsFilename, $string);
}
// saving table results in a text file
private function saveString(string $fileName, string $data): void {
// save string [$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]");
}
}
}
تعليقات
- [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]:
- مصفوفة [‘errors’=>[…]] تحتوي على السمات الخاطئة إذا كانت البيانات غير صحيحة؛
- مصفوفة فارغة إذا كانت البيانات صحيحة؛
- السطر 25: عندما تكون البيانات صالحة. يتم إنشاء كائن [TaxPayerData] جديد وإضافته إلى المصفوفة [$taxPayersData]؛
- الأسطر 26-30: الحالة التي تكون فيها البيانات غير صالحة. يتضمن سجل الخطأ معرف كائن [TaxPayerData] غير الصحيح في ملف JSON حتى يتمكن المستخدم من تحديد موقعه، ثم تتم إضافة الخطأ إلى المصفوفة [$errors]؛
- الأسطر 35-39: يتم تسجيل الأخطاء التي تمت مواجهتها في ملف JSON [$errorsFilename] الذي تم تمريره كمعلمة في السطر 9؛
- السطر 41: يتم إرجاع مصفوفة كائنات [TaxPayerData] التي تم إنشاؤها: كان هذا هو هدف الأسلوب؛
- الأسطر 44–83: تتحقق الطريقة الخاصة [check] من صحة المعلمات [married, children, salary] للمصفوفة [$attributesOfTaxPayerData] التي تم تمريرها كمعلمة في السطر 44. إذا كانت هناك أي سمات غير صالحة، فإنها تجمعها في المصفوفة [$attributes] (الأسطر 47 و53 و60 و70) في شكل مصفوفة [‘invalid attribute’=> قيمة السمة غير الصالحة]؛
- السطر 78: إذا كانت هناك أخطاء، يتم إرجاع مصفوفة [‘errors’=>$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
namespace Application;
// definition of a ImpotsWithDataInDatabase class
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
// use of a line
use TraitDao;
// the TaxAdminData object containing tax bracket data
private $taxAdminData;
// the [Database] type object containing the characteristics of the BD
private $database;
// manufacturer
public function __construct(string $databaseFilename) {
// store the jSON configuration of the bd
$this->database = (new Database())->setFromJsonFile($databaseFilename);
// we prepare the attribute
$this->taxAdminData = new TaxAdminData();
try {
// open the database connection
$connexion = new \PDO(
$this->database->getDsn(),
$this->database->getId(),
$this->database->getPwd());
// we want every SGBD error to trigger an exception
$connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// start a transaction
$connexion->beginTransaction();
// fill in the tax bracket table
$this->getTranches($connexion);
// fill in the constants table
$this->getConstantes($connexion);
// the transaction is completed successfully
$connexion->commit();
} catch (\PDOException $ex) {
// is there a transaction in progress?
if (isset($connexion) && $connexion->inTransaction()) {
// transaction ends in failure
$connexion->rollBack();
}
// trace the exception back to the calling code
throw new ExceptionImpots($ex->getMessage());
} finally {
// close the connection
$connexion = NULL;
}
}
// reading data from the database
private function getTranches($connexion): void {
…
}
// reading the constants table
private function getConstantes($connexion): void {
…
}
// returns data for tax calculation
public function getTaxAdminData(): TaxAdminData {
return $this->taxAdminData;
}
}
تعليقات
- السطر 4: نحتفظ بمساحة الاسم المستخدمة بالفعل في تطبيقات طبقة [dao] الأخرى؛
- السطر 7: تنفذ فئة [DaoImportsWithTaxAdminDataInDatabase] واجهة [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] للإشارة إلى فئة PHP [PDO]. نظرًا لأننا في مساحة اسم [Application]، إذا كتبنا ببساطة [PDO]، فسيتم إضافة مساحة الاسم الحالية كبادئة لهذا الاسم النسبي، مما ينتج عنه الفئة [Application\PDO]، التي لا وجود لها؛
- السطر 28: في حالة حدوث خطأ، سيقوم نظام إدارة قواعد البيانات (DBMS) بإلقاء استثناء \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 {
// raccourcis
$database = $this->database;
$taxAdminData = $this->taxAdminData;
// on prépare la requête SELECT
$statement = $connexion->prepare(
"select {$database->getColLimites()}," .
" {$database->getColCoeffR()}," .
" {$database->getColCoeffN()}" .
" from {$database->getTableTranches()}");
// on exécute l'ordre préparé avec les valeurs des tranches d'impôts
$statement->execute();
// on exploite le résultat
$limites = [];
$coeffR = [];
$coeffN = [];
// remplissage des trois tableaux
while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
$limites[] = (float) $tranche->{$database->getColLimites()};
$coeffR[] = (float) $tranche->{$database->getColCoeffR()};
$coeffN[] = (float) $tranche->{$database->getColCoeffN()};
}
// on mémorise les données dans l'attribut [$taxAdminData] de la classe
$taxAdminData->setFromArrayOfAttributes([
"limites" => $limites,
"coeffR" => $coeffR,
"coeffN" => $coeffN
]);
}
تعليقات
- السطر 1: تستقبل الطريقة [$connexion] كمعلمة، وهي اتصال مفتوح مع معاملة جارية؛
- الأسطر 2–4: يتم إنشاء اختصارين لتجنب الحاجة إلى كتابة [$this->database] و [$taxAdminData = $this->taxAdminData] في جميع أنحاء الكود. هذه نسخ من مراجع الكائنات، وليست نسخًا من الكائنات نفسها؛
- الأسطر 6-10: يتم إعداد عبارة SELECT، ثم تنفيذها في السطر 12؛
- الأسطر 13–22: تتم معالجة نتيجة SELECT. يتم تخزين المعلومات المستلمة في ثلاثة مصفوفات [limits، coeffR، coeffN]؛
- الأسطر 24–28: تُستخدم المصفوفات الثلاثة لتهيئة سمة [$this->taxAdminData] للفئة؛
الطريقة الخاصة [getConstantes] هي كما يلي:
private function getConstantes($connexion): void {
// raccourcis
$database = $this->database;
$taxAdminData = $this->taxAdminData;
// on prépare la requête 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);
// on exécute l'ordre préparé
$statement->execute();
// on exploite le résultat - 1 seule ligne ici
$row = $statement->fetch(\PDO::FETCH_OBJ);
// on initialise l'attribut [$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: تتلقى الطريقة [$connection] كمعلمة، وهي اتصال مفتوح مع معاملة جارية؛
- الأسطر 2–4: يتم إنشاء اختصارين لتجنب الحاجة إلى كتابة [$this->database] و [$taxAdminData = $this->taxAdminData] في جميع أنحاء الكود. هذه نسخ من مراجع الكائنات، وليست نسخًا من الكائنات نفسها؛
- الأسطر 6-15: يتم إعداد عبارة SELECT، ثم تنفيذها في السطر 15؛
- الأسطر 17–29: تتم معالجة نتيجة SELECT. تُستخدم المعلومات المسترجعة لتهيئة سمة [$this->taxAdminData] للفئة؛
ملاحظة: لاحظ أن الفئة لا تعتمد على نظام إدارة قواعد البيانات MySQL. إن الكود المستدعي هو الذي يحدد نظام إدارة قواعد البيانات المستخدم عبر DSN قاعدة البيانات.
13.4.4. طبقة [الأعمال]

- لقد قمنا للتو بتنفيذ طبقة [DAO] (3)؛
- نظرًا لأننا التزمنا بواجهة [InterfaceDao]، يمكن أن تظل طبقة [business] (2) دون تغيير من الناحية النظرية. ومع ذلك، لم نقم فقط بتعديل طبقة [DAO]. بل قمنا أيضًا بتعديل الكيانات، التي تشترك فيها جميع الطبقات؛
تنفذ طبقة [business] واجهة [BusinessInterface] التالية:
<?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;
}
- السطر 12: تستخدم طريقة [executeBatchImports] الآن ملف JSON [$taxPayersFileName]، بينما كانت تستخدم ملفًا نصيًا عاديًا في الإصدار 04. ;
في الإصدار 04، كانت طريقة [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);
}
- السطر 15 أصبح الآن غير صحيح. في التعريف الجديد لفئة [TaxPayerData]، لم تعد طريقة [setMontant] موجودة؛
في الإصدار 05، ستكون طريقة [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->setFromArrayOfAttributes($result);
// put the result in the results table
$results [] = $taxPayerData;
}
// recording results
$this->dao->saveResults($resultsFileName, $results);
}
تعليقات
- السطر 15: بدلاً من استخدام أدوات التعيين الفردية لفئة [TaxPayerData]، نستخدم أداة التعيين العامة [setFromArrayOfAttributes]؛
- لا حاجة لتعديل بقية الكود؛
13.4.5. النص البرمجي الرئيسي

- لقد قمنا للتو بتنفيذ طبقتي [DAO] (3) و[منطق الأعمال] (2)؛
- ولا يزال يتعين علينا كتابة البرنامج النصي الرئيسي (1)؛
النص البرمجي الرئيسي مشابه لذلك الموجود في الإصدار 04:
<?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__ . "/../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";
//
// definition of constants
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 {
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_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 "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;
تعليقات
- الأسطر 12–22: تحميل جميع الملفات من الإصدار 05؛
- الأسطر 25–29: أسماء ملفات JSON المختلفة للتطبيق؛
- السطر 33: إنشاء طبقة [DAO]؛
- السطر 35: إنشاء طبقة [business]؛
- السطر 37: استدعاء طريقة [executeBatchImports] لطبقة [business]؛
النتائج
يقوم التطبيق بإنشاء ملفين JSON:
- [results.json]: نتائج الحسابات الضريبية المختلفة؛
- [errors.json]: الذي يبلغ عن الأخطاء الموجودة في ملف JSON [taxpayersdata.json]؛
ملف [errors.json] هو كما يلي:
{
"numéro": 1,
"erreurs": [
{
"marié": "ouix"
},
{
"enfants": "2x"
},
{
"salaire": "55555x"
}
]
}
هذا يعني أن في ملف [taxpayersdata.json]، الإدخال الأول في جدول taxpayers غير صحيح. كان ملف [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
}
]
ملف النتائج [results.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
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
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();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
// calculation constants
$this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());
…
}
}
تعليقات
- الأسطر 9–33: تعريف بيئة الاختبار. نستخدم نفس البيئة المستخدمة في البرنامج النصي الرئيسي [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] الموصوف في القسم المرتبط؛
- الأسطر 39–44: إنشاء طبقة [dao]؛
- السطر 43: تحتوي السمة [$this→taxAdminData] على البيانات المراد اختبارها؛
- الأسطر 47–51: طريقة [testTaxAdminData] هي الطريقة الموضحة في القسم المرتبط؛
نتائج الاختبار هي كما يلي:

13.5.2. اختبار طبقة [business]
اختبار [MetierTest.php] هو كما يلي:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
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 {
// business layer
private $métier;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_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 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] و[business]؛
- السطر 44: تشير السمة [$this→business] إلى طبقة [business]؛
- الأسطر 47–64: الطرق [test1، test2…، test11] هي تلك الموصوفة في القسم المرتبط؛
نتائج الاختبار هي كما يلي:
