Skip to content

13. تمرین عملی – نسخه ۵

Image

ما قبلاً چندین نسخه از این تمرین را نوشته‌ایم. آخرین نسخه از معماری لایه‌لایه استفاده می‌کرد:

Image

لایه [dao] رابط [InterfaceDao] را پیاده‌سازی می‌کند. ما کلاسی ایجاد کرده‌ایم که این رابط را پیاده‌سازی می‌کند:

  • [DaoImpotsWithTaxAdminDataInJsonFileکه داده‌های مالیاتی را از فایلی به نام jSON بازیابی می‌کرد؛

ما رابط [InterfaceDao] را با استفاده از یک کلاس جدید، [DaoImpotsWithTaxAdminDataInDatabaseپیاده‌سازی خواهیم کرد که داده‌ها را از مرجع مالیاتی از پایگاه داده‌ای به نام MySQL بازیابی می‌کند.

13.1. ایجاد پایگاه داده [dbimpots-2019]

با دنبال کردن مثال در پاراگراف پیوندی، ما در حال ایجاد یک پایگاه داده با نام MySQL هستیم، با نام کاربری [dbimpots-2019]، متعلق به [admimpots] و با رمز عبور [mdpimpots]:

Image

  • در [1-4] بالا، می‌توانیم پایگاه داده [dbimpots-2019] را ببینیم که در حال حاضر هیچ جدولی ندارد؛

Image

  • در [1-5] بالا، می‌توانیم ببینیم که کاربر [admimpots] دارای حقوق کامل بر روی پایگاه داده [dbimpots-2019] است. آنچه در اینجا نمی‌توانیم ببینیم این است که این کاربر رمز عبور [admimpots] را دارد؛

اکنون جدول [tbtranches] را ایجاد می‌کنیم که شامل رده‌های مالیاتی خواهد بود:

Image

  • در [1-7]، ما جدولی به نام [tbtranches] با ۴ ستون ایجاد می‌کنیم؛

Image

  • در [3-6]، ستونی به نام [id] تعریف می‌کنیم (3)، از نوع عدد صحیح [int] (4)، که کلید اصلی [6] جدول خواهد بود و توسط SGBD به صورت خودکار افزایش می‌یابد [5]. این بدان معناست که MySQL خود در زمان درج، مقادیر کلید اصلی را مدیریت خواهد کرد. این مقدار را برای اولین درج روی 1، سپس برای درج بعدی روی 2 و به همین ترتیب تنظیم خواهد کرد؛
  • در [7]، ویزارد گزینه‌های پیکربندی بیشتری را برای کلید اصلی در اختیار ما قرار می‌دهد. در اینجا، ما به سادگی مقادیر پیش‌فرض را برای [7] می‌پذیریم؛

Image

  • در [8-16]، سه ستون دیگر جدول به شرح زیر تعریف شده‌اند:
    • [limites] (8)، از نوع عدد اعشاری (9) با 10 رقم، شامل 2 رقم اعشاری (10)، حاوی ورودی‌های ستون 17 مربوط به طبقات مالیاتی خواهد بود؛
    • [coeffR] (11)، از نوع عدد اعشاری (12) با 6 رقم، شامل 2 رقم اعشاری (13)، حاوی ورودی‌های ستون 18 جدول نرخ‌های مالیاتی خواهد بود؛
    • [coeffN] (14)، یک عدد اعشاری (15) با 10 رقم، شامل 2 رقم اعشاری (16)، حاوی داده‌های ستون 19 جدول‌های مالیاتی خواهد بود؛

پس از اعتبارسنجی این ساختار، نتیجه زیر به دست می‌آید:

Image

  • در [5]، آیکون کلید نشان می‌دهد که ستون [id] کلید اصلی است. همچنین می‌توانیم ببینیم که این کلید اصلی دارای مقادیر صحیح (6) است و توسط MySQL مدیریت (خودافزایشی) می‌شود؛

به همان شیوه‌ای که جدول [tbtranches] را ایجاد کردیم، جدول [tbconstantes] را ایجاد می‌کنیم که حاوی ثابت‌های مورد استفاده در محاسبه مالیات خواهد بود:

Image

امکان خروجی گرفتن ساختار پایگاه داده به یک فایل متنی به صورت دنباله‌ای از دستورات SQL وجود دارد:

Image

گزینه [5] در اینجا فقط ساختار پایگاه داده را صادر می‌کند، نه محتویات آن. در مورد ما، پایگاه داده هنوز حاوی هیچ داده‌ای نیست.

Image

Image

Image

گزینه [11] فایل زیر را تولید می‌کند: SQL [dbimpots-2019.sql]:


-- phpMyAdmin SQL Dump
-- نسخهٔ ۴.۸.۵
-- https://www.phpmyadmin.net/
--
-- میزبان: localhost:3306
--زمان تولید: ۳۰ ژوئن ۲۰۱۹ در ساعت ۰۱:۱۰ 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 خود این کار را انجام می‌دهد:

Image

Image

13.2. سازماندهی کد

برای تشریح بهتر نقش اسکریپت‌های مختلف PHP که می‌نویسیم، کد خود را در پوشه‌ها سازماندهی خواهیم کرد:

Image

  • در [1]، مروری بر نسخه 05؛
  • در [2]، اجزای برنامه، که بین لایه‌ها مبادله می‌شوند؛
  • در [3]، ابزارهای کاربردی برنامه؛
  • در [4]، داده‌های مورد استفاده یا تولیدشده توسط برنامه. ما در اینجا تصمیم گرفته‌ایم فقط از فایل‌های jSON برای فایل‌های متنی استفاده کنیم. این فایل‌ها چندین مزیت دارند:
    • آنها توسط ابزارهای بسیاری شناسایی می‌شوند؛
    • این ابزارها قابلیت هایلایت کردن سینتکس را دارند. علاوه بر این، نشانه نگاری jSON از قوانین خاصی پیروی می‌کند. هنگامی که به این قوانین پایبند نباشند، ابزارها آن‌ها را علامت‌گذاری می‌کنند. برای مثال، خطایی که تشخیص آن در یک فایل متنی ساده دشوار است، استفاده از حروف بزرگ یا کوچک «O» به جای صفرها است. اگر این خطا رخ دهد، علامت‌گذاری خواهد شد. در واقع، در کد jSON:

"plafondRevenusCouplePourReduction": 42O74

در کد [42074] که در آن به‌طور سهوی از حرف بزرگ «O» به جای صفر استفاده شده است، NetBeans این خطا را علامت‌گذاری می‌کند:

Image

در واقع، NetBeans حرف O بزرگ را تشخیص می‌دهد که [49O74] را به یک رشته تبدیل می‌کند. این نرم‌افزار نتیجه می‌گیرد که نحو باید [4-5] باشد: رشته [47O74] باید در گیومه قرار گیرد. بنابراین توجه توسعه‌دهنده به خطا جلب می‌شود و می‌تواند آن را اصلاح کند: یا با افزودن علامت‌های نقل قول، یا با جایگزینی حرف «O» با عدد صفر؛

سایر عناصر در نسخه 05 به شرح زیر هستند:

Image

  • در [6]، رابط‌ها و کلاس‌های لایه [Dao]؛
  • در [7]، رابط‌ها و کلاس‌های لایه [métier]؛
  • در [8]، اسکریپت‌های اصلی برای نسخه 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. اشیاء

Image

اِنتیتی [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 را از فایل پیکربندی در یک آرایهٔ asociative فراخوانی می‌کند
      $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;
  }

}

توضیحات

  • خط ۵: کلاس [BaseEntity] قرار است توسط کلاس‌های [Database, TaxAdminData, TaxPayerData] گسترش یابد؛
  • خط ۷: ویژگی [$arrayOfAttributes] یک آرایه است که شامل تمام ویژگی‌های کلاس فرزند (child class) است که [BaseEntity] را گسترش داده است، به همراه مقادیر آن‌ها؛
  • خطوط ۹–۴۱: ویژگی [$arrayOfAttributes] از فایل jSON که به‌عنوان پارامتر ارسال شده، مقداردهی اولیه می‌شود. اگر فایل jSON قابل خواندن نباشد یا یک فایل معتبر jSON نباشد، یک استثنا از نوع [ExceptionImpot] پرتاب می‌شود؛
  • خطوط ۳۶–۳۸: این کد ویژه است که برای اجرا توسط یک کلاس مشتق در نظر گرفته شده است. در این مورد، [$this] نماد یک نمونه از کلاس فرزند [Database, TaxAdminData, TaxPayerData] است و در این نمونه، خطوط ۳۶–۳۸ ویژگی‌های این کلاس فرزند را مقداردهی اولیه می‌کنند، مشروط بر اینکه این ویژگی‌ها دید «محافظت‌شده» (یا «عمومی») داشته باشند (به بند مرتبط مراجعه کنید). در واقع بیان شده بود که ویژگی‌های اشیاء [Database, TaxAdminData, TaxPayerData] همان ویژگی‌های فایل jSON بود که آن‌ها را در بر می‌گرفتند. در نهایت، متد [setFromJsonFile] اجازه می‌دهد یک کلاس فرزند از یک فایل jSON مقداردهی اولیه شود؛
  • خط ۴۰: اگر متد [setFromJsonFile] توسط یک کلاس فرزند فراخوانی شده باشد، شیء [$this] – یعنی یک نمونه از یک کلاس فرزند – بازگردانده می‌شود؛
  • خطوط ۴۳–۵۱: متد [checkForAllAttributes] به یک کلاس فرزند اجازه می‌دهد بررسی کند که همهٔ ویژگی‌هایش مقداردهی شده‌اند. اگر این‌طور نباشد، یک استثنای [ExceptionImpots] پرتاب می‌شود. این متد به کلاس فرزند اجازه می‌دهد تا بررسی کند که فایل jSON آن هیچ صفتی را از قلم نیانداخته است؛
  • خطوط ۵۳–۶۰: متد [setFromArrayOfAttributes] به یک کلاس فرزند اجازه می‌دهد تا همه یا برخی از ویژگی‌های خود را از یک آرایهٔ انجمنی که کلیدهای آن نام‌هایی مشابه ویژگی‌های کلاس فرزند دارند، مقداردهی اولیه کند؛
  • خطوط ۶۳–۷۰: متد [__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
شناسه
مالک پایگاه داده
رمز عبور
رمز عبور
tableTranches
نام جدولی که شامل مقاطع مالیاتی است
colLimites
colCoeffR
colCoeffN
نام ستون‌ها در جدول [tableTranches]
tableConstantes
نام جدولی که حاوی ثابت‌های محاسبه مالیات است
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
نام ستون‌ها در جدول [tableConstantes] که حاوی ثابت‌های محاسبه مالیات است

چرا باید به جداول و ستون‌ها نام بدهیم وقتی از قبل نامشان را می‌دانیم و احتمال تغییرشان کم است؟ پس از SGBD و MySQL، از SGBD و PostgreSQL برای ذخیره داده‌های مرجع مالیاتی استفاده خواهیم کرد. با این حال، نام ستون‌ها و جداول Postgres از همان قواعد MySQL پیروی نمی‌کنند. ما باید از نام‌های متفاوتی استفاده کنیم. این موضوع در مورد سایر نمونه‌های SGBD نیز صدق می‌کند. اگر بخواهیم کد در سراسر نمونه‌های 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;
  }



}

توضیحات

  • خطوط ۷–۲۴: تمام ویژگی‌های کلاس دید [protected] را دارند. این امر ضروری است تا بتوان آن‌ها را از کلاس والد [BaseEntity] تغییر داد (به بخش «لینک» مراجعه کنید)؛
  • خطوط ۲۸–۳۵: متد [setFromJsonFile] اجازه می‌دهد ویژگی‌های کلاس [Database] با استفاده از محتویات فایلی به نام jSON که به‌عنوان پارامتر ارسال می‌شود، مقداردهی اولیه شوند. ویژگی‌های فایل jSON و ویژگی‌های کلاس [Database] باید یکسان باشند. اگر فایل jSON قابل پردازش نباشد، یک استثنا پرتاب می‌شود؛
  • خط ۳۰: کلاس والد اقدام به مقداردهی اولیه می‌کند؛
  • خط ۳۲: از کلاس والد خواسته می‌شود تا بررسی کند که همه ویژگی‌های کلاس [Database] مقداردهی شده‌اند. اگر اینطور نباشد، یک استثنا پرتاب می‌شود؛
  • خط ۳۴: نمونه [Database] که به تازگی مقداردهی اولیه شده است، بازگردانده می‌شود؛
  • خطوط ۳۷ به بعد: گترها و سترها برای ویژگی‌های کلاس؛

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 یک آرایه از عناصر رشته‌ای یا یک عنصر واحد است
    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;
  }

  // گیرنده و تنظیم‌کننده

}

توضیحات

  • خط ۲۳: متد [setFromJsonFile] برای مقداردهی اولیه ویژگی‌های کلاس [TaxAdminData] از یک فایل jSON که به‌عنوان پارامتر ارسال می‌شود، استفاده می‌شود. ویژگی‌ها در فایل jSON باید با همان نام‌ها در داخل کلاس وجود داشته باشند؛
  • خط ۲۵: این کار توسط کلاس والد انجام می‌شود؛
  • خط ۲۷: از کلاس والد خواسته می‌شود تا بررسی کند که همه ویژگی‌های کلاس فرزند مقداردهی اولیه شده‌اند؛
  • خطوط 29–42: ما به‌صورت محلی بررسی می‌کنیم که به همه ویژگی‌ها یک مقدار حقیقی مثبت یا صفر اختصاص داده شده است. این بررسی قبلاً در بخش «لینک» نسخه 03 مورد بحث قرار گرفته است؛

13.3.3. لایه [dao]

اکنون می‌توانیم کد انتقال داده‌ها از فایل متنی [taxadmindata.json] به جداول [tbtranches, tbconstantes] در پایگاه داده MySQL [dbimpots-2019] را بنویسیم. ما از معماری زیر استفاده خواهیم کرد:

Image

Image

لایه [dao] رابط زیر [InterfaceDao4TransferAdminDataFromFile2Database] را پیاده‌سازی خواهد کرد:


<?php

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

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

نظرات

  • خط ۸: متد [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 آموخته‌ایم به کار می‌بریم.

  • خط ۷: کلاس [DaoTransferAdminDataFromJsonFile2Database] رابط [InterfaceDao4TransferAdminData2Database] را پیاده‌سازی می‌کند؛
  • خط ۹: ویژگی [$database] یک شی از نوع [Database] است که داده‌های فایل [database.json] را در بر می‌گیرد؛
  • خط ۱۱: ویژگی [$taxAdminData] یک شی از نوع [TaxAdminData] است که داده‌های فایل [taxadmindata.json] را در بر می‌گیرد؛
  • خطوط ۱۴–۱۹: سازنده نام فایل‌های [database.json, taxadmindata.json] را به‌عنوان پارامتر دریافت می‌کند؛
  • خط ۱۶: مقداردهی اولیه ویژگی [$database];
  • خط ۱۸: مقداردهی اولیه ویژگی [$taxAdminData];
  • خط ۲۳: تنها متد رابط [InterfaceDao4TransferAdminData2Database] پیاده‌سازی شده است؛
  • خطوط 26–38: جداول [tbtranches, tbconstantes] در دو مرحله پر می‌شوند:
    • خط ۳۴: جدول [tbtranches] ابتدا پر می‌شود. این کار در داخل یک تراکنش (خطوط ۳۲ و ۳۸) انجام می‌شود. متد [fillTableTranches] (خط 55) به محض اینکه مشکلی پیش بیاید، یک استثنا پرتاب می‌کند. در این مورد، اجرای برنامه با بلاک catch / finally در خطوط 39–50 ادامه می‌یابد؛
    • خط ۳۶: جدول [tbconstantes] به همین روش با استفاده از متد [fillTableConstantes] (خط ۶۰) پر می‌شود؛
  • خطوط ۳۹–۴۷: حالتی که یک استثنا توسط کد پرتاب شده است؛
  • خطوط ۴۱–۴۴: اگر تراکنشی وجود داشته باشد، آن رول‌بک می‌شود؛
  • خط ۴۶: یک استثنا از نوع [ExceptionImpots] پرتاب می‌شود، به همراه پیامی از استثنای اصلی که از نوع نامشخص است؛
  • خطوط ۴۷–۵۰: در بند [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]]);
    }
  }

توضیحات

  • خط ۱: متد [fillTableTranches] یک اتصال باز را به عنوان پارامتر می‌پذیرد. ما همچنین می‌دانیم که یک تراکنش در این اتصال آغاز شده است؛
  • خطوط ۵–۷: مقادیری که باید در جدول درج شوند توسط ویژگی [$taxAdminData] ارائه می‌شوند؛
  • خطوط ۹–۱۰: محتوای فعلی جدول [tbtranches] حذف می‌شود؛
  • خطوط ۱۲–۱۵: درج ردیف‌ها در جدول آماده می‌شود. نام ستون‌های ارائه‌شده توسط ویژگی [$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()
    ]);
  }

توضیحات

  • خط ۱: متد [fillTableConstantes] یک اتصال باز را به عنوان پارامتر دریافت می‌کند. ما همچنین می‌دانیم که یک تراکنش در این اتصال آغاز شده است؛
  • خطوط ۵–۶: جدول [tbconstantes] پاک می‌شود؛
  • خطوط ۹–۳۱: آماده‌سازی عبارت درج SQL. این کار پیچیده است زیرا در این عملیات درج، باید ۱۰ ستون را مقداردهی اولیه کرد و نام ستون‌ها باید از ویژگی [$database] بازیابی شود؛
  • خطوط ۳۳–۴۴: اجرای دستور درج. تنها یک ردیف برای درج وجود دارد. در اینجا نیز، کد به دلیل نیاز به بازیابی مقادیر قابل درج از ویژگی [$taxAdminData] پیچیده شده است؛

13.3.4. اسکریپت اصلی

Image

Image

اسکریپت اصلی برای انجام انتقال داده‌ها به لایه [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;

توضیحات

  • خطوط ۱۲–۲۱: بارگذاری کلاس‌ها و رابط‌های برنامه؛
  • خطوط 24–24: دو فایل jSON؛
  • خط ۳۰: ایجاد نمونه از لایه [dao] با ارسال دو فایل jSON به سازنده؛
  • خط ۳۲: انتقال داده‌ها انجام می‌شود؛

وقتی این کد را اجرا می‌کنیم، نتیجه زیر را در پایگاه داده به دست می‌آوریم:

Image

در ستون [3] می‌توانیم مقادیری را که توسط MySQL به کلید اصلی [id] اختصاص داده شده است، مشاهده کنیم. شماره‌گذاری از عدد 1 شروع می‌شود. اسکرین‌شات بالا پس از اجرای چندین بار اسکریپت گرفته شده است.

Image

Image

13.4. محاسبه مالیات

Image

13.4.1. معماری

نسخه ۰۴ از برنامه محاسبه مالیات از معماری لایه‌لایه استفاده می‌کرد:

Image

لایه [dao] رابط [InterfaceDao] را پیاده‌سازی می‌کند. ما کلاسی ایجاد کردیم که این رابط را پیاده‌سازی می‌کند:

  • [DaoImpotsWithTaxAdminDataInJsonFileکه داده‌های مالیاتی را از فایلی به نام jSON بازیابی می‌کرد. این نسخه ۰۴ بود؛

ما رابط [InterfaceDao] را با استفاده از کلاس جدید [DaoImpotsWithTaxAdminDataInDatabase] پیاده‌سازی خواهیم کرد که داده‌ها را از مرجع مالیاتی از پایگاه‌داده با نام MySQL بازیابی می‌کند. لایه [dao]، مانند قبل، نتایج و خطاها را در فایل‌های متنی می‌نویسد و همچنین داده‌های مودی را از یک فایل متنی بازیابی می‌کند. با این حال، در این مورد، این فایل‌های متنی فایل‌های jSON خواهند بود. علاوه بر این، می‌دانیم که اگر به استفاده از رابط [InterfaceDao] ادامه دهیم، لایه [métier] نیازی به اصلاح نخواهد داشت.

Image

13.4.2. واحد [TaxPayerData]

Image

کلاس [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;

  // گیرنده‌ها و تنظیم‌کننده‌ها

}

توضیحات

  • خط ۷: کلاس [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 = [];
    //وضعیت تأهل باید 'bله' یا 'خیر' باشد
    $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] را پیاده‌سازی می‌کند؛
  • خط ۹: متد [getTaxPayersData] پارامترهای زیر را می‌پذیرد:
    • [$taxPayersFilename]: نام فایل jSON حاوی داده‌های مودی [taxpayersdata.json]؛
    • [$errorsFilename]: نام فایل jSON حاوی خطاها [errors.json];
  • خطوط ۱۱–۱۳: محتویات فایل jSON حاوی داده‌های مالیات‌دهنده به یک آرایهٔ asociative [$arrayOfAttributes] منتقل می‌شوند. اگر فایل jSON غیرقابل استفاده تشخیص داده شود، یک استثنای [ExceptionImpots] ایجاد شده است؛
  • خط ۱۵: آرایه [$taxPayersData] حاوی داده‌های مالیات‌دهنده خواهد بود که در اشیایی از نوع [TaxPayerData] بسته‌بندی شده‌اند؛
  • خط ۱۷: خطاها در آرایه [$errors] انباشته خواهند شد؛
  • خطوط ۹۹–۳۳: ایجاد جدول [$taxPayersData];
  • خط ۲۲: قبل از اینکه داده‌ها در نوع [TaxPayerData] بسته‌بندی شوند، بررسی می‌شوند. متد [check] بازمی‌گرداند:
    • یک آرایه [‘erreurs’=>[…]] حاوی ویژگی‌های نادرست اگر داده‌ها نادرست باشند؛
    • یک آرایه خالی اگر داده‌ها صحیح باشند؛
  • خط ۲۵: زمانی که داده‌ها معتبر هستند. یک شیء جدید [TaxPayerData] ایجاد شده و به آرایه [$taxPayersData] اضافه می‌شود؛
  • خطوط ۲۶–۳۰: حالتی که داده‌ها نامعتبر هستند. لاگ خطا شمارهٔ شیء معیوب [TaxPayerData] را در فایل jSON ثبت می‌کند تا کاربر بتواند آن را پیدا کند، و پس از آن خطا به آرایه [$errors] اضافه می‌شود؛
  • خطوط ۳۵–۳۹: خطاهای رخ‌داده در فایل jSON ثبت می‌شوند ([$errorsFilename] به‌عنوان پارامتر ارسال شده است، خط ۹);
  • خط ۴۱: آرایهٔ اشیاء ساخته‌شدهٔ [TaxPayerData] بازگردانده می‌شود: این هدف متد بود؛
  • خطوط 44–83: متد خصوصی [check] اعتبار پارامترهای [marié, enfants, salaire] را در آرایه [$attributesOfTaxPayerData] که به‌عنوان پارامتر در خط 44 ارسال شده است، بررسی می‌کند. اگر هرگونه ویژگی نادرست وجود داشته باشد، آن‌ها را در آرایه [$attributes] (خطوط 47، 53، 60، 70) در قالب یک آرایه [‘attribut erroné’=> valeur de lattribut erroné] جمع‌آوری می‌کند؛
  • خط ۷۸: اگر خطاهایی وجود داشته باشد، یک آرایه [‘erreurs’=>$attributes] بازگردانده می‌شود؛
  • خط ۸۱: اگر خطایی وجود نداشته باشد، یک آرایه خطای خالی بازگردانده می‌شود؛
  • خطوط ۸۶–۹۳: پیاده‌سازی متد [saveResults] از رابط [InterfaceDao];
  • خط ۹۰: رشته jSON ساخته می‌شود تا در فایل jSON [$resultsFilename] که به‌عنوان پارامتر در خط ۸۶ ارسال شده است، نوشته شود. رشته jSON باید از یک آرایه ساخته شود:
    • هر عنصر آرایه با یک ویرگول و یک خط جدید از عنصر بعدی جدا می‌شود؛
    • کل آرایه در کروشه‌های مربعی [] قرار گرفته است؛
  • خط ۹۲: رشته 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;
  }

}

نظرات

  • خط ۴: فضای نامی که قبلاً برای پیاده‌سازی‌های دیگر لایه [dao] استفاده شده است، حفظ می‌شود؛
  • خط ۷: کلاس [DaoImpotsWithTaxAdminDataInDatabase] رابط [InterfaceDao] را پیاده‌سازی می‌کند؛
  • خط ۹: ما تریت [TraitDao] را وارد می‌کنیم. می‌دانیم که این تریت بخشی از رابط را پیاده‌سازی می‌کند. تنها متدی که باقی مانده تا پیاده‌سازی شود، متد [getTaxAdminData] در خطوط 62–64 است. این متد به سادگی ویژگی خصوصی [taxAdminData] از خط 11 را برمی‌گرداند. از این می‌توان نتیجه گرفت که سازنده (constructor) باید این ویژگی را مقداردهی اولیه کند. این تنها هدف آن است؛
  • خط 16: سازنده یک پارامتر واحد به نام [$databaseFilename] دریافت می‌کند که نام فایلی به نام jSON [database.json] است که پایگاه داده MySQL [dbimpots-2019] را تعریف می‌کند ;
  • خط ۱۸: فایل jSON [$databaseFilename] برای ایجاد یک شی از نوع [Database] استفاده می‌شود که در ویژگی [$database] در خط ۱۳ ساخته و ذخیره می‌شود. اگر فایل jSON نتوانست به درستی پردازش شود، یک استثنای [ExceptionImpots] پرتاب شد؛
  • خط ۲۰: شیء [$this→taxAdminData] ایجاد می‌شود که سازنده باید آن را مقداردهی اولیه کند؛
  • خطوط ۲۲–۲۶: اتصال به پایگاه داده برقرار می‌شود. به نشانه‌گذاری [\PDO] توجه کنید که به کلاس [PDO] از PHP اشاره دارد. این به این دلیل است که از آنجا که ما در فضای نام [Application] هستیم، اگر صرفاً [PDO] را بنویسیم، این نام نسبی با فضای نام فعلی پیش‌وند می‌شود و بنابراین به کلاس [Application\PDO] که وجود ندارد، ارجاع می‌دهد؛
  • خط ۲۸: در صورت بروز خطا، SGBD یک \PDOException را فعال می‌کند (خط ۳۷);
  • خط ۳۰: یک تراکنش آغاز می‌شود. این کار واقعاً ضروری نیست زیرا تنها دو دستور SQL اجرا خواهند شد که هیچ‌کدام پایگاه داده را تغییر نمی‌دهند. با این حال، این کار برای ایزوله کردن فرآیند از سایر کاربران پایگاه داده انجام می‌شود؛
  • خط ۳۲: جدول طبقات مالیاتی [tbtranches] با استفاده از متد خصوصی [getTranches] در خط ۵۲ خوانده می‌شود؛
  • خط ۳۴: جدول ثابت‌های محاسباتی [tbconstantes] با استفاده از متد خصوصی [getConstantes] در خط ۵۷ خوانده می‌شود؛
  • خط ۳۶: اگر به این خط برسیم، یعنی همه چیز به خوبی پیش رفته است. بنابراین تراکنش را نهایی می‌کنیم؛
  • خطوط ۳۷–۴۲: اگر به این نقطه برسیم، یعنی یک استثنا رخ داده است. بنابراین اگر تراکنش در حال انجام باشد، آن را لغو می‌کنیم (خطوط ۳۹–۴۲). خط ۴۴: برای اطمینان از یکپارچگی استثناءها، پیام استثناء دریافتی را مجدداً پرتاب می‌کنیم، این بار به عنوان استثناء از نوع [ExceptionImpots]؛
  • خطوط ۴۵–۴۸: در همه موارد (چه استثنایی رخ داده باشد یا نه)، اتصال را می‌بندیم؛

متد [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
    ]);
  }

توضیحات

  • خط ۱: متد [$connexion] را به عنوان پارامتر دریافت می‌کند، که یک اتصال باز است که در آن یک تراکنش در حال انجام است؛
  • خطوط ۲–۴: دو میانبر ایجاد می‌شوند تا نیازی به نوشتن [$this->database] و [$taxAdminData = $this->taxAdminData] در سراسر کد نباشد. این‌ها کپی‌های ارجاع به اشیاء هستند، نه کپی‌های خود اشیاء؛
  • خطوط ۶–۱۰: دستور SELECT آماده شده و سپس در خط ۱۲ اجرا می‌شود؛
  • خطوط ۱۳–۲۲: نتیجه 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()});
  }

توضیحات

  • خط ۱: متد [$connexion] را به‌عنوان پارامتر می‌پذیرد، که یک اتصال باز است و در آن یک تراکنش در حال انجام است؛
  • خطوط ۲–۴: دو میانبر ایجاد شده‌اند تا نیازی به نوشتن [$this->database] و [$taxAdminData = $this->taxAdminData] در سراسر کد نباشد. این‌ها کپی‌های ارجاع به اشیاء هستند، نه کپی‌های خود اشیاء؛
  • خطوط ۶–۱۵: دستور SELECT آماده شده و سپس در خط ۱۵ اجرا می‌شود؛
  • خطوط 17–29: نتیجه SELECT پردازش می‌شود. اطلاعات بازیابی‌شده برای مقداردهی اولیه ویژگی [$this->taxAdminData] کلاس استفاده می‌شود؛

توجه: باید توجه داشت که کلاس به SGBD یا MySQL وابسته نیست. این کد فراخوانی است که SGBD را از طریق DSN از پایگاه داده تنظیم می‌کند.

13.4.4. لایه [métier]

Image

  • ما به تازگی لایه [dao] (۳) را پیاده‌سازی کرده‌ایم؛
  • از آنجا که ما به رابط [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;
}
  • خط ۱۲: متد [executeBatchImpots] اکنون از فایل jSON [$taxPayersFileName] استفاده می‌کند، در حالی که در نسخه ۰۴ این یک فایل متنی ساده بود.

در نسخه 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);
  }
  • خط ۱۵ اکنون نادرست است. در تعریف جدید کلاس [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);
  }

توضیحات

  • خط ۱۵: به جای استفاده از سترهای مجزای کلاس [TaxPayerData]، از ستر کلی آن [setFromArrayOfAttributes] استفاده می‌شود؛
  • بقیه کد نیازی به تغییر ندارد؛

13.4.5. اسکریپت اصلی

Image

  • ما به تازگی لایه‌های [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;


نظرات

  • خطوط ۱۲–۲۲: بارگذاری تمام فایل‌ها برای نسخه ۰۵؛
  • خطوط ۲۵–۲۹: نام فایل‌های مختلف jSON در برنامه؛
  • خط ۳۳: ساخت لایه [dao];
  • خط ۳۵: ساخت لایه [métier];
  • خط ۳۷: فراخوانی متد [executeBatchImpots] از لایه [métier];

نتایج

برنامه دو فایل jSON تولید می‌کند:

  • [resultats.json]: نتایج محاسبات مالیاتی مختلف؛
  • [errors.json]: که خطاهای یافته‌شده در فایل‌های jSON و [taxpayersdata.json] را گزارش می‌کند؛

فایل [errors.json] به شرح زیر است:


{
    "numéro": 1,
    "erreurs": [
        {
            "marié": "ouix"
        },
        {
            "enfants": "2x"
        },
        {
            "salaire": "55555x"
        }
    ]
}

این بدان معناست که در [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 خواهیم نوشت.

Image

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());

  }

}

توضیحات

  • خطوط ۹–۳۳: تعریف محیط آزمایش. ما از همان محیطی استفاده می‌کنیم که در اسکریپت اصلی [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] که در بخش مرتبط توصیف شده است، به کار رفته است؛
  • خطوط ۳۹–۴۴: ساخت لایه [dao]؛
  • خط ۴۳: ویژگی [$this→taxAdminData] حاوی داده‌های مورد آزمایش است؛
  • خطوط ۴۷–۵۱: روش [testTaxAdminData] همان روشی است که در پاراگراف «link» توضیح داده شده است؛

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

Image

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"]);
  }

}

توضیحات

  • خطوط ۹–۳۳: تعریف محیط آزمایش. ما از همان محیطی استفاده می‌کنیم که در اسکریپت اصلی [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] که در بخش مرتبط توصیف شده است، به کار رفته است؛
  • خطوط ۳۹–۴۵: ساخت لایه‌های [dao] و [métier];
  • خط ۴۴: ویژگی [$this→métier] به لایه [métier] ارجاع می‌دهد؛
  • خطوط ۴۷–۶۴: متدهای [test1, test2…, test11] همان‌هایی هستند که در بخش «لینک» توضیح داده شده‌اند؛

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

Image