Skip to content

13. Практичне завдання — версія 5

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], що має 4 стовпці;

Image

  • у [3-6] ми визначаємо стовпець з назвою [id] (3), типу цілого числа [int] (4), який буде первинним ключем [6] таблиці та автоматично інкрементуватиметься [5] за допомогою SGBD. Це означає, що 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 Зливання
-- версія 4.8.5
-- https://www.phpmyadmin.net/
--
-- Хост: localhost:3306
-- Час створення: 30 червня 2019 р. о 01:10 PM
-- Версія сервера: 5.7.24
-- PHP Версія: 7.2.11

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- База даних: `dbimpots-2019`
--
CREATE DATABASE IF NOT EXISTS `dbimpots-2019` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `dbimpots-2019`;

-- --------------------------------------------------------

--
-- Структура таблиці `tbconstantes`
--

DROP TABLE IF EXISTS `tbconstantes`;
CREATE TABLE `tbconstantes` (
  `id` int(11) NOT NULL,
  `plafondQfDemiPart` decimal(10,2) NOT NULL,
  `plafondRevenusCelibatairePourReduction` decimal(10,2) NOT NULL,
  `plafondRevenusCouplePourReduction` decimal(10,2) NOT NULL,
  `valeurReducDemiPart` decimal(10,2) NOT NULL,
  `plafondDecoteCelibataire` decimal(10,2) NOT NULL,
  `plafondDecoteCouple` decimal(10,2) NOT NULL,
  `plafondImpotCelibatairePourDecote` decimal(10,2) NOT NULL,
  `plafondImpotCouplePourDecote` decimal(10,2) NOT NULL,
  `abattementDixPourcentMax` decimal(10,2) NOT NULL,
  `abattementDixPourcentMin` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Структура таблиці `tbtranches`
--

DROP TABLE IF EXISTS `tbtranches`;
CREATE TABLE `tbtranches` (
  `id` int(11) NOT NULL,
  `limites` decimal(10,2) NOT NULL,
  `coeffR` decimal(10,2) NOT NULL,
  `coeffN` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Індекси для вивантажених таблиць
--

--
-- Індекси для таблиці `tbconstantes`
--
ALTER TABLE `tbconstantes`
  ADD PRIMARY KEY (`id`);

--
-- Індекси для таблиці `tbtranches`
--
ALTER TABLE `tbtranches`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT для вивантажених таблиць
--

--
-- AUTO_INCREMENT для таблиці `tbconstantes`
--
ALTER TABLE `tbconstantes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT для таблиці `tbtranches`
--
ALTER TABLE `tbtranches`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Ви можете використати цей файл SQL для відновлення бази даних [dbimpots-2019], якщо вона була знищена або пошкоджена. У цьому випадку немає необхідності видаляти базу даних перед її відновленням, оскільки скрипт SQL робить це самостійно:

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;

Версія 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 з файлу конфігурації заноситься в асоціативний масив
      $this->arrayOfAttributes = \json_decode($fileContents, true);
      // помилка?
      if ($this->arrayOfAttributes === FALSE) {
        // зафіксовано помилку
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
      }
    }
    // помилка?
    if ($erreur) {
      // генерується виняток
      throw new ExceptionImpots($message);
    }
    // ініціалізація атрибутів класу
    foreach ($this->arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // повертається об’єкт
    return $this;
  }

  public function checkForAllAttributes() {
    // перевіряється, чи всі ключі були ініціалізовані
    foreach (\array_keys($this->arrayOfAttributes) as $key) {
      if ($key !== "arrayOfAttributes" && !isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de la classe "
          . get_class($this) . " n'a pas été initialisé");
      }
    }
  }

  public function setFromArrayOfAttributes(array $arrayOfAttributes) {
    // ініціалізуються деякі атрибути класу
    foreach ($arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // повертається об’єкт
    return $this;
  }

  // toString
  public function __toString() {
    // атрибути об’єкта
    $arrayOfAttributes = \get_object_vars($this);
    // видаляється атрибут батьківського класу
    unset($arrayOfAttributes["arrayOfAttributes"]);
    // JSON-рядок об’єкта
    return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
  }

  // геттер
  public function getArrayOfAttributes() {
    return $this->arrayOfAttributes;
  }

}

Коментарі

  • рядок 5: клас [BaseEntity] призначений для розширення класами [Database, TaxAdminData, TaxPayerData];
  • рядок 7: атрибут [$arrayOfAttributes] — це масив, що містить усі атрибути дочірнього класу, який розширив [BaseEntity], а також їхні значення;
  • рядки 9–41: атрибут [$arrayOfAttributes] ініціалізується на основі файлу jSON [$jsonFilename], переданого як параметр. Виникає виняток типу [ExceptionImpot], якщо файл jSON не вдалося прочитати або якщо це не дійсний файл jSON;
  • рядки 36–38: тут міститься спеціальний код, який виконується, якщо функцію викликає дочірній клас. У цьому випадку [$this] представляє екземпляр дочірнього класу [Database, TaxAdminData, TaxPayerData], і в такому випадку рядки 36–38 ініціалізують атрибути цього дочірнього класу, за умови, що ці атрибути мають рівень видимості protected (або public) (див. параграф «посилання»). Адже було зазначено, що атрибути сутностей [Database, TaxAdminData, TaxPayerData] є такими самими, як і атрибути файлу jSON, який вони інкапсулюють. Нарешті, метод [setFromJsonFile] дозволяє дочірньому класу ініціалізуватися на основі файлу jSON;
  • рядок 40: об’єкт [$this], тобто екземпляр дочірнього класу, повертається, якщо метод [setFromJsonFile] був викликаний дочірнім класом;
  • рядки 43–51: метод [checkForAllAttributes] дозволяє дочірньому класу перевірити, чи всі його атрибути були ініціалізовані. Якщо це не так, генерується виняток [ExceptionImpots]. Цей метод дозволяє дочірньому класу перевірити, чи не пропущено в його файлі jSON певні атрибути;
  • рядки 53–60: метод [setFromArrayOfAttributes] дозволяє дочірньому класу ініціалізувати всі або частину своїх атрибутів на основі асоціативного масиву, ключі якого мають ті самі імена, що й атрибути дочірнього класу, який потрібно ініціалізувати;
  • рядки 63–70: метод [__toString] дозволяє отримати представлення jSON дочірнього класу;

13.3.2.2. Суть [Database]

Суть [Database] має такий вигляд:


<?php

namespace Application;

class Database extends BaseEntity {
  // атрибути
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;



}

Клас [Database] використовується для інкапсуляції даних наступного файлу jSON [database.json]:


{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

Клас і файл jSON мають однакові атрибути. Вони описують характеристики бази даних MySQL [dbimpots-2019]:

dsn
Назва бази даних DSN
id
Власник бази даних
pwd
Його пароль
tableTranches
Назва таблиці, що містить податкові діапазони
colLimites
colCoeffR
colCoeffN
Назви стовпців таблиці [tableTranches]
tableConstantes
Назва таблиці, що містить константи для розрахунку податку
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
Назви стовпців таблиці [tableConstantes], що містять константи для розрахунку податку

Навіщо називати таблиці та стовпці, якщо їхні назви вже відомі і вони не підлягають зміні? Після SGBD MySQL ми будемо використовувати SGBD PostgreSQL для зберігання даних податкової адміністрації. Однак імена стовпців і таблиць Postgres не відповідають тим самим правилам, що й MySQL. Нам доведеться використовувати інші імена. Це також стосується інших таблиць SGBD. Якщо ми хочемо, щоб код був сумісним між таблицями SGBD, то краще використовувати параметри, а не жорстко задані імена таблиць і стовпців.

Повернемося до коду класу [Database]:


<?php

namespace Application;

class Database extends BaseEntity {
  // атрибути
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;

  // сеттер
  // ініціалізація
  public function setFromJsonFile(string $jsonFilename) {
    // батьківський елемент
    parent::setFromJsonFile($jsonFilename);
    // перевіряється, чи всі атрибути були ініціалізовані
    parent::checkForAllAttributes();
    // повертається об’єкт
    return $this;
  }

  // гетери та сеттери
  public function getDsn() {
    return $this->dsn;
  }



  public function setDsn($dsn) {
    $this->dsn = $dsn;
    return $this;
  }



}

Коментарі

  • рядки 7–24: усі атрибути класу мають видимість [protected]. Це є умовою того, щоб їх можна було змінювати з батьківського класу [BaseEntity] (див. параграф «посилання»);
  • рядки 28–35: метод [setFromJsonFile] дозволяє ініціалізувати атрибути класу [Database] на основі вмісту файлу jSON, переданого як параметр. Атрибути файлу jSON та класу [Database] мають бути ідентичними. Якщо файл jSON не підходить для обробки, генерується виняток;
  • рядок 30: ініціалізацію виконує батьківський клас;
  • рядок 32: батьківський клас отримує запит перевірити, чи всі атрибути класу [Database] були ініціалізовані. Якщо це не так, генерується виняток;
  • рядок 34: повертається екземпляр [Database], який щойно було ініціалізовано;
  • рядки 37 і далі: методи getter та setter для атрибутів класу;

13.3.2.3. Об’єкт [TaxAdminData]

Об’єкт [TaxAdminData] має такий вигляд:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // податкові шкали
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // константи для розрахунку податку
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  
}

Клас [TaxAdminData] використовується для інкапсуляції даних з наступного файлу jSON [taxadmindata.json]:


{
    "limites": [
        9964,
        27519,
        73779,
        156244,
        0
    ],
    "coeffR": [
        0,
        0.14,
        0.3,
        0.41,
        0.45
    ],
    "coeffN": [
        0,
        1394.96,
        5798,
        13913.69,
        20163.45
    ],
    "plafondQfDemiPart": 1551,
    "plafondRevenusCelibatairePourReduction": 21037,
    "plafondRevenusCouplePourReduction": 42074,
    "valeurReducDemiPart": 3797,
    "plafondDecoteCelibataire": 1196,
    "plafondDecoteCouple": 1970,
    "plafondImpotCouplePourDecote": 2627,
    "plafondImpotCelibatairePourDecote": 1595,
    "abattementDixPourcentMax": 12502,
    "abattementDixPourcentMin": 437
}

Клас і файл jSON мають однакові атрибути. Вони представляють дані податкової адміністрації. Решта коду класу [TaxAdminData] виглядає так:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // податкові шкали
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // константи для розрахунку податку
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  // ініціалізація
  public function setFromJsonFile(string $taxAdminDataFilename) {
    // батьківський елемент
    parent::setFromJsonFile($taxAdminDataFilename);
    // перевіряється, чи всі атрибути були ініціалізовані
    parent::checkForAllAttributes();
    // перевіряється, чи значення атрибутів є дійсними числами >=0
    foreach ($this as $key => $value) {
      if ($key !== "arrayOfAttributes") {
        // $value має бути дійсним числом >=0 або масивом дійсних чисел >=0
        $result = $this->check($value);
        // помилка?
        if ($result->erreur) {
          // генерується виняток
          throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
        } else {
          // записується значення
          $this->$key = $result->value;
        }
      }
    }
    // повертається об’єкт
    return $this;
  }

  protected function check($value): \stdClass {
    // $value — це масив елементів типу string або один елемент
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // масив рядків перетворюється на масив дійсних чисел
    $newTableau = [];
    $result = new \stdClass();
    // елементи масиву мають бути додатними або нульовими десятковими числами
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        // поміщаємо число типу float у newTableau
        $newTableau[] = (float) $tableau[$i];
      } else {
        // фіксуємо помилку
        $result->erreur = TRUE;
        // програма завершується
        return $result;
      }
    }
    // повертаємо результат
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      // одне значення
      $result->value = $newTableau[0];
    } else {
      // список значень
      $result->value = $newTableau;
    }
    return $result;
  }

  // гетери та сеттери

}

Коментарі

  • рядок 23: метод [setFromJsonFile] слугує для ініціалізації атрибутів класу [TaxAdminData] на основі файлу jSON, переданого як параметр. Атрибути файлу jSON повинні існувати під тими самими іменами у класі;
  • рядок 25: цю роботу виконує батьківський клас;
  • рядок 27: батьківський клас перевіряє, чи всі атрибути дочірнього класу були ініціалізовані;
  • рядки 29–42: локально перевіряється, чи всі атрибути мають дійсне додатне або нульове значення. Ця перевірка вже обговорювалася в розділі «Посилання» версії 03;

13.3.3. Рівень [dao]

Тепер ми можемо написати код, який перенесе дані з текстового файлу [taxadmindata.json] у таблиці [tbtranches, tbconstantes] бази даних MySQL [dbimpots-2019]. Ми будемо дотримуватися такої архітектури:

Image

Image

Рівень [dao] реалізує наступний інтерфейс [InterfaceDao4TransferAdminDataFromFile2Database]:


<?php

// простір імен
namespace Application;

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

Коментарі

  • рядок 8: метод [transferAdminData2Database] призначений для зберігання даних податкової адміністрації в базі даних;

Інтерфейс [InterfaceDao4TransferAdminData2Database] буде реалізовано наступним класом [DaoTransferAdminDataFromJsonFile2Database]:


<?php

// простір імен
namespace Application;

// визначення класу TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
  // атрибути цільової бази даних
  private $database;
  // дані податкової адміністрації
  private $taxAdminData;

  // виробник
  public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
    // зберігається конфігурація бази даних
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // зберігаються податкові дані
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // передає дані про податкові ставки з текстового файлу
  // до бази даних
  public function transferAdminData2Database(): void {
    // робота з базою даних
    $database = $this->database;
    try {
      // відкривається з'єднання з базою даних
      $connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
      // потрібно, щоб при кожній помилці SGBD генерувалося виключення
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // запускаємо транзакцію
      $connexion->beginTransaction();
      // заповнюємо таблицю податкових шкал
      $this->fillTableTranches($connexion);
      // заповнюється таблиця констант
      $this->fillTableConstantes($connexion);
      // транзакція успішно завершується
      $connexion->commit();
    } catch (\PDOException $ex) {
      // чи є транзакція, що виконується?
      if (isset($connexion) && $connexion->inTransaction()) {
        // завершення транзакції з помилкою
        $connexion->rollBack();
      }
      // видається виняток у код, що викликав
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // закрито з'єднання
      $connexion = NULL;
    }
  }


  // заповнення таблиці податкових шкал
  private function fillTableTranches($connexion): void {

  }

  // заповнення таблиці констант
  private function fillTableConstantes($connexion): void {

  }

}

Коментарі

Тут ми використовуємо те, що вивчили в розділі про MySQL.

  • рядок 7: клас [DaoTransferAdminDataFromJsonFile2Database] реалізує інтерфейс [InterfaceDao4TransferAdminData2Database];
  • рядок 9: атрибут [$database] є об’єктом типу [Database], що інкапсулює дані файлу [database.json];
  • рядок 11: атрибут [$taxAdminData] є об’єктом типу [TaxAdminData], що інкапсулює дані файлу [taxadmindata.json];
  • рядки 14–19: конструктор отримує як параметри імена файлів [database.json, taxadmindata.json];
  • рядок 16: ініціалізація атрибута [$database];
  • рядок 18: ініціалізація атрибута [$taxAdminData];
  • рядок 23: реалізується єдиний метод інтерфейсу [InterfaceDao4TransferAdminData2Database];
  • рядки 26–38: заповнюємо таблиці [tbtranches, tbconstantes] у два етапи:
    • рядок 34: спочатку заповнюється таблиця [tbtranches]. Це відбувається в рамках транзакції (рядки 32, 38). Метод [fillTableTranches] (рядок 55) генерує виняток, щойно щось йде не так. У цьому випадку виконання продовжується з блоком catch / finally у рядках 39–50;
    • рядок 36: таблицю [tbconstantes] заповнюють аналогічним чином за допомогою методу [fillTableConstantes] (рядок 60);
  • рядки 39–47: випадок, коли кодом було згенеровано виняток;
  • рядки 41–44: якщо транзакція існує, її скасовують;
  • рядок 46: генерується виняток типу [ExceptionImpots] із повідомленням про вихідний виняток, який, у свою чергу, може бути будь-якого типу;
  • рядки 47–50: у блоці [finally] з’єднання закривається;

Код методу [fillTableTranches] такий:


private function fillTableTranches($connexion): void {
    // скорочення для БД
    $database = $this->database;
    // дані для введення в базу даних
    $limites = $this->taxAdminData->getLimites();
    $coeffR = $this->taxAdminData->getCoeffR();
    $coeffN = $this->taxAdminData->getCoeffN();
    // очищення таблиці на випадок, якщо в ній щось є
    $statement = $connexion->prepare("delete from " . $database->getTableTranches());
    $statement->execute();
    // підготовка записів для вставки
    $sqlInsert = "insert into {$database->getTableTranches()} "
      . "({$database->getColLimites()}, {$database->getColCoeffR()},"
      . " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
    $statement = $connexion->prepare($sqlInsert);
    // виконуємо підготовлену команду із значеннями податкових шкал
    for ($i = 0; $i < count($limites); $i++) {
      $statement->execute([
        "limites" => $limites[$i],
        "coeffR" => $coeffR[$i],
        "coeffN" => $coeffN[$i]]);
    }
  }

Коментарі

  • рядок 1: метод [fillTableTranches] отримує як параметр відкрите з’єднання. Крім того, відомо, що в межах цього з’єднання розпочалася транзакція;
  • рядки 5–7: значення, які потрібно вставити в таблицю, надаються атрибутом [$taxAdminData];
  • рядки 9–10: видаляється поточний вміст таблиці [tbtranches];
  • рядки 12–15: готується вставлення рядків у таблицю. Тут використовуються імена стовпців, надані атрибутом [$database];
  • рядки 17–22: виконується стільки разів, скільки потрібно, інструкція вставки, підготовлена в рядках 12–15;

Код методу [fillTableConstantes] такий:


private function fillTableConstantes($connexion): void {
    // скорочений варіант
    $database = $this->database;
    // очищаємо таблицю на випадок, якщо в ній щось є
    $statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
    $statement->execute();
    // підготовка вставки
    $taxAdminData = $this->taxAdminData;
    $sqlInsert = "insert into {$database->getTableConstantes()}"
      . " ({$database->getColPlafondQfDemiPart()},"
      . " {$database->getColPlafondRevenusCelibatairePourReduction()},"
      . " {$database->getColPlafondRevenusCouplePourReduction()},"
      . " {$database->getColValeurReducDemiPart()},"
      . " {$database->getColPlafondDecoteCelibataire()},"
      . " {$database->getColPlafondDecoteCouple()},"
      . " {$database->getColPlafondImpotCelibatairePourDecote()},"
      . " {$database->getColPlafondImpotCouplePourDecote()},"
      . " {$database->getColAbattementDixPourcentMax()},"
      . " {$database->getColAbattementDixPourcentMin()})"
      . " values ("
      . ":plafondQfDemiPart,"
      . ":plafondRevenusCelibatairePourReduction,"
      . ":plafondRevenusCouplePourReduction,"
      . ":valeurReducDemiPart,"
      . ":plafondDecoteCelibataire,"
      . ":plafondDecoteCouple,"
      . ":plafondImpotCelibatairePourDecote,"
      . ":plafondImpotCouplePourDecote,"
      . ":abattementDixPourcentMax,"
      . ":abattementDixPourcentMin)";
    $statement = $connexion->prepare($sqlInsert);
    // виконується підготовлений запит
    $statement->execute([
      "plafondQfDemiPart" => $taxAdminData->getPlafondQfDemiPart(),
      "plafondRevenusCelibatairePourReduction" => $taxAdminData->getPlafondRevenusCelibatairePourReduction(),
      "plafondRevenusCouplePourReduction" => $taxAdminData->getPlafondRevenusCouplePourReduction(),
      "valeurReducDemiPart" => $taxAdminData->getValeurReducDemiPart(),
      "plafondDecoteCelibataire" => $taxAdminData->getPlafondDecoteCelibataire(),
      "plafondDecoteCouple" => $taxAdminData->getPlafondDecoteCouple(),
      "plafondImpotCelibatairePourDecote" => $taxAdminData->getPlafondImpotCelibatairePourDecote(),
      "plafondImpotCouplePourDecote" => $taxAdminData->getPlafondImpotCouplePourDecote(),
      "abattementDixPourcentMax" => $taxAdminData->getAbattementDixPourcentMax(),
      "abattementDixPourcentMin" => $taxAdminData->getAbattementDixPourcentMin()
    ]);
  }

Коментарі

  • рядок 1: метод [fillTableConstantes] отримує як параметр відкрите з’єднання. Крім того, відомо, що в рамках цього з’єднання розпочалася транзакція;
  • рядки 5–6: таблиця [tbconstantes] очищується;
  • рядки 9–31: підготовка команди вставки SQL. Вона є складною, оскільки в цій операції вставки потрібно ініціалізувати 10 стовпців, а імена стовпців необхідно отримати з атрибута [$database];
  • рядки 33–44: виконання команди вставки. Вставити потрібно лише один рядок. І тут знову код ускладнюється тим, що значення, які потрібно вставити, доводиться шукати в атрибуті [$taxAdminData];

13.3.4. Головний скрипт

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;

Коментарі

  • рядки 12–21: завантаження класів та інтерфейсів додатка;
  • рядки 24–24: два файли jSON;
  • рядок 30: створюється екземпляр шару [dao], передаючи конструктору обидва файли jSON;
  • рядок 32: виконується передача даних;

Коли ми виконуємо цей код, у базі даних отримуємо такий результат:

Image

У стовпці [3] видно значення, присвоєні MySQL первинному ключу [id]. Нумерація починається з 1. Наведений вище знімок екрана було отримано після декількох запусків скрипта.

Image

Image

13.4. Розрахунок податку

Image

13.4.1. Архітектура

У версії 04 програми для розрахунку податку використовувалася багаторівнева архітектура:

Image

Рівень [dao] реалізує інтерфейс [InterfaceDao]. Ми створили клас, що реалізує цей інтерфейс:

  • [DaoImpotsWithTaxAdminDataInJsonFile], який отримував податкові дані з файлу jSON. Це була версія 04;

Ми реалізуємо інтерфейс [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;

  // методи getter та setter

}

Коментарі

  • рядок 7: клас [TaxPayerData] успадковує клас [BaseEntity]. Оскільки методів його батьківського класу достатньо, клас [TaxPayerData] сам не визначає жодних методів. Нагадаємо, що атрибути класу [TaxPayerData] ідентичні атрибутам файлу jSON [taxpayersdata.json];

13.4.3. Рівень [dao]

13.4.3.1. Особливість [TraitDao]

Лінія [TraitDao] реалізує частину інтерфейсу [InterfaceDao]. Нагадаємо його:


<?php

// простір імен
namespace Application;

interface InterfaceDao {

  // читання даних про платників податків
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // читання даних податкової адміністрації (податкові шкали)
  public function getTaxAdminData(): TaxAdminData;

  // запис результатів
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Трайт [TraitDao] реалізує методи [getTaxPayersData, saveResults] інтерфейсу [InterfaceDao]. Оскільки між версіями 04 та 05 було змінено визначення сутності [TaxPayerData], нам потрібно переглянути код [TraitDao]:


<?php

// простір імен
namespace Application;

trait TraitDao {

  // читання даних платників податків
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // отримання даних про платників податків у таблицю
    $baseEntity = new BaseEntity();
    $baseEntity->setFromJsonFile($taxPayersFilename);
    $arrayOfAttributes = $baseEntity->getArrayOfAttributes();
    // масив даних про платників податків
    $taxPayersData = [];
    // масив помилок
    $errors = [];
    // цикл по масиву атрибутів елементів типу [TaxPayerData]
    $i = 0;
    foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
      // перевірка
      $error = $this->check($attributesOfTaxPayerData);
      if (!$error) {
        // платник з +
        $taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
      } else {
        // помилка + - фіксується номер недійсних даних
        $error = ["numéro" => $i] + $error;
        $errors[] = $error;
      }
      // далі
      $i++;
    }
    // зберігаємо помилки у файлі json
    $string = "";
    foreach ($errors as $error) {
      $string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
    }
    $this->saveString($errorsFilename, $string);
    // результат функції
    return $taxPayersData;
  }

  private function check(array $attributesOfTaxPayerData): array {
    // перевіряються дані з [$taxPayerData]
    // список атрибутів із помилками
    $attributes = [];
    // сімейний стан має бути «так» або «ні»
    $marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
    $erreur = ($marié !== "oui" and $marié !== "non");
    if ($erreur) {
      // фіксується помилка
      $attributes[] = ["marié" => $marié];
    }
    // кількість дітей має бути додатним цілим числом або нулем
    $enfants = trim($attributesOfTaxPayerData["enfants"]);
    if (!preg_match("/^\d+$/", $enfants)) {
      // позначено помилку
      $erreur = TRUE;
      $attributes[] = ["enfants" => $enfants];
    } else {
      $enfants = (int) $enfants;
    }

    // зарплата має бути додатним цілим числом або нулем (без центів)
    $salaire = trim($attributesOfTaxPayerData["salaire"]);
    if (!preg_match("/^\d+$/", $salaire)) {
      // зафіксовано помилку
      $erreur = TRUE;
      $attributes[] = ["salaire" => $salaire];
    } else {
      $salaire = (int) $salaire;
    }

    // помилка?
    if ($erreur) {
      // повернення з помилкою
      return ["erreurs" => $attributes];
    } else {
      // повернення без помилки
      return [];
    }
  }

  // запис результатів
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // запис таблиці [$taxPayersData] у текстовий файл [$resultsFileName]
    // якщо текстовий файл [$resultsFileName] не існує, його створюють
    // формування рядка jSON із результатів
    $string = "[" . implode(",
", $taxPayersData) . "]";
    // запис цього рядка
    $this->saveString($resultsFilename, $string);
  }

  // запис результатів з масиву у текстовий файл
  private function saveString(string $fileName, string $data): void {
    // запис рядка [$data] у текстовий файл [$fileName]
    // якщо текстовий файл [$fileName] не існує, його створюють
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Коментарі

  • [TraitDao] реалізує методи [getTaxPayersData] (рядок 9) та [saveResults] (рядок 86) інтерфейсу [InterfaceDao];
  • рядок 9: метод [getTaxPayersData] отримує як параметри:
    • [$taxPayersFilename]: ім’я файлу jSON із даними платників податків [taxpayersdata.json];
    • [$errorsFilename]: ім’я файлу jSON з помилками [errors.json];
  • рядки 11–13: вміст файлу jSON з даними платників податків переноситься в асоціативний масив [$arrayOfAttributes]. Якщо файл jSON виявився непридатним для обробки, було згенеровано виняток [ExceptionImpots];
  • рядок 15: масив [$taxPayersData] міститиме дані платників податків, інкапсульовані в об’єкти типу [TaxPayerData];
  • рядок 17: помилки будуть накопичуватися в масиві [$errors];
  • рядки 99–33: формування таблиці [$taxPayersData];
  • рядок 22: перед інкапсуляцією в тип [TaxPayerData] дані перевіряються. Метод [check] повертає:
    • масив [‘erreurs’=>[…]] з атрибутами, що містять помилки, якщо дані є некоректними;
    • порожній масив, якщо дані правильні;
  • рядок 25: випадок, коли дані є правильними. Створюється новий об’єкт [TaxPayerData], який додається до масиву [$taxPayersData];
  • рядки 26–30: випадок, коли дані є недійсними. У повідомленні про помилку зазначається номер помилкового об’єкта [TaxPayerData] у файлі jSON, щоб користувач міг його знайти, після чого помилка додається до масиву [$errors];
  • рядки 35–39: записуються помилки, виявлені у файлі jSON, переданому як параметр у файлі [$errorsFilename], рядок 9;
  • рядок 41: повертається масив створених об’єктів [TaxPayerData]: це було метою методу;
  • рядки 44–83: приватний метод [check] перевіряє правильність параметрів [marié, enfants, salaire] масиву [$attributesOfTaxPayerData], переданого як параметр у рядку 44. Якщо є помилкові атрибути, вона накопичує їх у масиві [$attributes] (рядки 47, 53, 60, 70) у вигляді масиву [‘attribut erroné’=> valeur de l’attribut erroné];
  • рядок 78: якщо є помилки, повертається масив [‘erreurs’=>$attributes];
  • рядок 81: якщо помилок немає, повертається порожній масив помилок;
  • рядки 86–93: реалізація методу [saveResults] інтерфейсу [InterfaceDao];
  • рядок 90: формується рядок jSON для запису у файл jSON [$resultsFilename], переданий як параметр у рядку 86. Необхідно сформувати рядок jSON з масиву:
    • кожен елемент масиву відокремлений від наступного комою та перенесенням рядка;
    • весь масив укладено в квадратні дужки [];
  • рядок 92: рядок jSON записується у файл jSON [$resultsFilename];

13.4.3.2. Клас [DaoImpotsWithTaxAdminDataInDatabase]

Клас [DaoImpotsWithTaxAdminDataInDatabase] реалізує інтерфейс [InterfaceDao] наступним чином:


<?php

// простір імен
namespace Application;

// визначення класу ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
  // використання риси
  use TraitDao;
  // об’єкт типу TaxAdminData, що містить дані про податкові групи
  private $taxAdminData;
  // об’єкт типу [Database], що містить характеристики BD
  private $database;

  // конструктор
  public function __construct(string $databaseFilename) {
    // зберігається конфігурація jSON бази даних
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // готуємо атрибут
    $this->taxAdminData = new TaxAdminData();
    try {
      // відкриваємо з'єднання з базою даних
      $connexion = new \PDO(
        $this->database->getDsn(),
        $this->database->getId(),
        $this->database->getPwd());
      // потрібно, щоб при кожній помилці SGBD генерувалося виключення
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // запускаємо транзакцію
      $connexion->beginTransaction();
      // заповнюємо таблицю податкових шкал
      $this->getTranches($connexion);
      // заповнюється таблиця констант
      $this->getConstantes($connexion);
      // транзакція успішно завершується
      $connexion->commit();
    } catch (\PDOException $ex) {
      // чи є транзакція, що виконується?
      if (isset($connexion) && $connexion->inTransaction()) {
        // завершення транзакції з помилкою
        $connexion->rollBack();
      }
      // видається виняток у код, що викликав
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // закрито з'єднання
      $connexion = NULL;
    }
  }

  // читання даних із бази
  private function getTranches($connexion): void {

  }

  // читання таблиці констант
  private function getConstantes($connexion): void {

  }

  // повертає дані, необхідні для розрахунку податку
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }

}

Коментарі

  • рядок 4: зберігається простір імен, який вже використовується для інших реалізацій шару [dao];
  • рядок 7: клас [DaoImpotsWithTaxAdminDataInDatabase] реалізує інтерфейс [InterfaceDao];
  • рядок 9: імпортується риса [TraitDao]. Відомо, що ця риса реалізує частину інтерфейсу. Єдиний метод, який залишається реалізувати, — це метод [getTaxAdminData] у рядках 62–64. Цей метод лише повертає приватний атрибут [taxAdminData] із рядка 11. Звідси випливає, що конструктор повинен ініціалізувати цей атрибут. Це його єдина роль;
  • рядок 16: конструктор отримує як єдиний параметр [$databaseFilename], що є іменем файлу jSON [database.json], який визначає базу даних MySQL [dbimpots-2019] ;
  • рядок 18: файл jSON [$databaseFilename] використовується для створення об’єкта типу [Database], який побудовано та збережено в атрибуті [$database] у рядку 13. Якщо файл jSON не вдалося правильно обробити, було згенеровано виняток [ExceptionImpots];
  • рядок 20: створюється об’єкт [$this→taxAdminData], який конструктор повинен ініціалізувати;
  • рядки 22–26: відкривається з’єднання з базою даних. Зверніть увагу на позначення [\PDO], яке позначає клас [PDO] з PHP. Адже, оскільки ми перебуваємо в просторі імен [Application], якби ми просто написали [PDO], до цього відносного імені додався б префікс поточного простору імен, і, отже, отримався б клас [Application\PDO], якого не існує;
  • рядок 28: у разі помилки SGBD запустить \PDOException (рядок 37);
  • рядок 30: запускається транзакція. Вона не є особливо корисною, оскільки будуть виконані лише два команди SQL, які не змінюють базу даних. Проте це робиться для ізоляції від інших користувачів бази даних;
  • рядок 32: читання таблиці податкових шкал [tbtranches] здійснюється за допомогою приватного методу [getTranches] із рядка 52;
  • рядок 34: читання таблиці розрахункових констант [tbconstantes] здійснюється за допомогою приватного методу [getConstantes] з рядка 57;
  • рядок 36: якщо виконано цей рядок, це означає, що все пройшло успішно. Отже, транзакцію підтверджують;
  • рядки 37–42: якщо ми дійшли до цього місця, значить, стався виняток. Тому ми скасовуємо транзакцію, якщо така була в процесі виконання (рядки 39–42). Рядок 44: щоб забезпечити однорідність винятків, ми повторно генеруємо повідомлення про отриманий виняток, цього разу у вигляді винятку типу [ExceptionImpots];
  • рядки 45–48: у будь-якому випадку (незалежно від того, чи стався виняток) закриваємо з’єднання;

Метод [getTranches] має такий вигляд:


private function getTranches($connexion): void {
     // скорочення
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
     // підготовка запиту SELECT
    $statement = $connexion->prepare(
      "select {$database->getColLimites()}," .
      " {$database->getColCoeffR()}," .
      " {$database->getColCoeffN()}" .
      " from {$database->getTableTranches()}");
     // виконання підготовленого запиту із значеннями податкових шкал
    $statement->execute();
     // аналізуємо результат
    $limites = [];
    $coeffR = [];
    $coeffN = [];
     // заповнення трьох таблиць
    while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
      $limites[] = (float) $tranche->{$database->getColLimites()};
      $coeffR[] = (float) $tranche->{$database->getColCoeffR()};
      $coeffN[] = (float) $tranche->{$database->getColCoeffN()};
    }
    // зберігаємо дані в атрибуті [$taxAdminData] класу
    $taxAdminData->setFromArrayOfAttributes([
      "limites" => $limites,
      "coeffR" => $coeffR,
      "coeffN" => $coeffN
    ]);
  }

Коментарі

  • рядок 1: метод отримує як параметр [$connexion] — це відкрите з’єднання, у якому триває транзакція;
  • рядки 2–4: створюються два скорочення, щоб уникнути необхідності писати [$this->database] та [$taxAdminData = $this->taxAdminData] у всьому коді. Тут маємо копії посилань на об’єкти, а не копії самих об’єктів;
  • рядки 6–10: готується команда SELECT, яка потім виконується в рядку 12;
  • рядки 13–22: використовується результат SELECT. Отримана інформація накопичується у трьох масивах [limites, coeffR, coeffN];
  • рядки 24–28: ці три масиви використовуються для ініціалізації атрибута [$this->taxAdminData] класу;

Приватний метод [getConstantes] має такий вигляд:


private function getConstantes($connexion): void {
    // скорочення
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // підготовка запиту SELECT
    $select = "select {$database->getColPlafondQfDemiPart()}," .
      "{$database->getColPlafondRevenusCelibatairePourReduction()}," .
      "{$database->getColPlafondRevenusCouplePourReduction()}," . "{$database->getColValeurReducDemiPart()}," .
      "{$database->getColPlafondDecoteCelibataire()}," . "{$database->getColPlafondDecoteCouple()}," .
      "{$database->getColPlafondImpotCelibatairePourDecote()}," . "{$database->getColPlafondImpotCouplePourDecote()}," .
      "{$database->getColAbattementDixPourcentMax()}," . "{$database->getColAbattementDixPourcentMin()}" .
      " from {$database->getTableConstantes()}";
    $statement = $connexion->prepare($select);
    // виконується підготовлений запит
    $statement->execute();
    // використовуємо результат — тут лише один рядок
    $row = $statement->fetch(\PDO::FETCH_OBJ);
    // ініціалізуємо атрибут [$taxAdminData]
    $taxAdminData->setPlafondQfDemiPart($row->{$database->getColPlafondQfDemiPart()});
    $taxAdminData->setPlafondRevenusCelibatairePourReduction(
      $row->{$database->getColPlafondRevenusCelibatairePourReduction()});
    $taxAdminData->setPlafondRevenusCouplePourReduction($row->{$database->getColPlafondRevenusCouplePourReduction()});
    $taxAdminData->setValeurReducDemiPart($row->{$database->getColValeurReducDemiPart()});
    $taxAdminData->setPlafondDecoteCelibataire($row->{$database->getColPlafondDecoteCelibataire()});
    $taxAdminData->setPlafondDecoteCouple($row->{$database->getColPlafondDecoteCouple()});
    $taxAdminData->setPlafondImpotCelibatairePourDecote($row->{$database->getColPlafondImpotCelibatairePourDecote()});
    $taxAdminData->setPlafondImpotCouplePourDecote($row->{$database->getColPlafondImpotCouplePourDecote()});
    $taxAdminData->setAbattementDixPourcentMax($row->{$database->getColAbattementDixPourcentMax()});
    $taxAdminData->setAbattementDixPourcentMin($row->{$database->getColAbattementDixPourcentMin()});
  }

Коментарі

  • рядок 1: метод отримує як параметр [$connexion], який є відкритим з’єднанням, у якому триває транзакція;
  • рядки 2–4: створюються два скорочення, щоб уникнути необхідності писати [$this->database] та [$taxAdminData = $this->taxAdminData] у всьому коді. Тут маємо копії посилань на об’єкти, а не копії самих об’єктів;
  • рядки 6–15: готується команда SELECT, яка потім виконується в рядку 15;
  • рядки 17–29: використовується результат SELECT. Отримана інформація використовується для ініціалізації атрибута [$this->taxAdminData] класу;

Примітка: слід зауважити, що клас не залежить від SGBD та MySQL. Саме код, що викликає, визначає SGBD, який використовується через DSN з бази даних.

13.4.4. Рівень [métier]

Image

  • ми щойно реалізували рівень [dao] (3);
  • оскільки ми дотрималися інтерфейсу [InterfaceDao], шар [métier] (2) теоретично може залишитися без змін. Однак ми змінили не лише шар [dao]. Ми також змінили об’єкти, які є спільними для всіх шарів;

Рівень [métier] реалізує наступний інтерфейс [InterfaceMetier]:


<?php

// простір імен
namespace Application;

interface InterfaceMetier {

  // розрахунок податків платника податків
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // розрахунок податків у пакетному режимі
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • рядок 12: метод [executeBatchImpots] тепер використовує файл jSON [$taxPayersFileName], тоді як у версії 04 це був звичайний текстовий файл;

У версії 04 метод [executeBatchImpots] мав такий вигляд:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // дозволяється передавати винятки, що походять з рівня [dao]
    // отримання даних про платників податків
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // таблиця результатів
    $results = [];
    // здійснюється їх обробка
    foreach ($taxPayersData as $taxPayerData) {
      // розраховується податок
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // заповнюємо [$taxPayerData]
      $taxPayerData->setMontant($result["impôt"]);
      $taxPayerData->setDécôte($result["décôte"]);
      $taxPayerData->setSurCôte($result["surcôte"]);
      $taxPayerData->setTaux($result["taux"]);
      $taxPayerData->setRéduction($result["réduction"]);
      // заносимо результат у таблицю результатів
      $results [] = $taxPayerData;
    }
    // запис результатів
    $this->dao->saveResults($resultsFileName, $results);
  }
  • рядок 15 тепер є помилковим. У новому визначенні класу [TaxPayerData] метод [setMontant] більше не існує;

У версії 05 метод [executeBatchImpots] матиме такий вигляд:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // дозволяємо виводити винятки, що надходять з рівня [dao]
    // отримуємо дані про платників податків
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // таблиця результатів
    $results = [];
    // аналізуються
    foreach ($taxPayersData as $taxPayerData) {
      // розраховується податок
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // заповнюємо [$taxPayerData]
      $taxPayerData->setFromArrayOfAttributes($result);
      // заносимо результат у таблицю результатів
      $results [] = $taxPayerData;
    }
    // запис результатів
    $this->dao->saveResults($resultsFileName, $results);
  }

Коментарі

  • рядок 15: замість використання окремих сеттерів класу [TaxPayerData] використовується його глобальний сеттер [setFromArrayOfAttributes];
  • решту коду змінювати не потрібно;

13.4.5. Головний скрипт

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;


Коментарі

  • рядки 12–22: завантаження всіх файлів версії 05;
  • рядки 25–29: імена різних файлів jSON програми;
  • рядок 33: побудова шару [dao];
  • рядок 35: побудова шару [métier];
  • рядок 37: виклик методу [executeBatchImpots] шару [métier];

Результати

Додаток створює два файли jSON:

  • [resultats.json]: результати різних розрахунків податків;
  • [errors.json]: у якому вказано помилки, виявлені у файлах jSON та [taxpayersdata.json];

Файл [errors.json] має такий вигляд:


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

Це означає, що у файлі [taxpayersdata.json] елемент № 1 таблиці платників податків містить помилку. Файл [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());

  }

}

Коментарі

  • рядки 9–33: визначення середовища тесту. Ми використовуємо те саме середовище, що й у головному скрипті [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], описаному в розділі «Посилання»;
  • рядки 39–44: побудова шару [dao];
  • рядок 43: атрибут [$this→taxAdminData] містить дані, що підлягають тестуванню;
  • рядки 47–51: метод [testTaxAdminData] — це той, що описаний у параграфі «посилання»;

Результати тесту такі:

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

}

Коментарі

  • рядки 9–33: визначення середовища тестування. Ми використовуємо те саме середовище, що й у головному скрипті [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], описаному в розділі «Посилання»;
  • рядки 39–45: побудова шарів [dao] та [métier];
  • рядок 44: атрибут [$this→métier] посилається на шар [métier];
  • рядки 47–64: методи [test1, test2…, test11] — це ті, що описані в розділі «посилання»;

Результати тесту такі:

Image