8. Практичне завдання – версія 3
Повернемося до вправи, яку ми вже розглядали раніше (параграфи 4.3 та 4.4), щоб вирішити її за допомогою коду PHP із використанням класу.
8.1. Структура скриптів

8.2. Виняток [ExceptionImpots]
У версії 03, коли конструктор або метод класу зустріне помилку, він викличе виняток типу [ExceptionImpots] наступного вигляду:
Коментарі
- рядок 4: клас [ExceptionImpots] знаходиться в просторі імен [Application];
- рядок 6: клас [ExceptionImpots] успадковує клас, попередньо визначений у PHP [RuntimeException];
- рядок 8: конструктор очікує два параметри:
- $message: це повідомлення про помилку, пов’язане з винятком;
- $code: код помилки, пов’язаний із винятком. Якщо його немає, використовується код 0;
8.3. Клас [TaxAdminData]
У версії 02 дані податкової адміністрації були зібрані:
- спочатку у файл jSON;
- потім із цього файлу jSON — в асоціативну таблицю;
У версії 03 дані податкової адміністрації як і раніше містяться у файлі [taxadmindata.json], але з іншими назвами атрибутів:
{
"limites": [
9964,
27519,
73779,
156244,
0
],
"coeffR": [
0,
0.14,
0.3,
0.41,
0.45
],
"coeffN": [
0,
1394.96,
5798,
13913.69,
20163.45
],
"plafondQfDemiPart": 1551,
"plafondRevenusCelibatairePourReduction": 21037,
"plafondRevenusCouplePourReduction": 42074,
"valeurReducDemiPart": 3797,
"plafondDecoteCelibataire": 1196,
"plafondDecoteCouple": 1970,
"plafondImpotCouplePourDecote": 2627,
"plafondImpotCelibatairePourDecote": 1595,
"abattementDixPourcentMax": 12502,
"abattementDixPourcentMin": 437
}
У версії 02 цей файл використовувався для ініціалізації асоціативного масиву. У версії 03 цей файл ініціалізує наступний клас [TaxAdminData]:
<?php
namespace Application;
class TaxAdminData {
// податкові шкали
private $limites;
private $coeffR;
private $coeffN;
// константи розрахунку податку
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// ініціалізація
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// отримано вміст файлу податкових даних
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// помилка?
if (!$fileContents) {
// фіксуємо помилку
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// завантажуємо код jSON із файлу конфігурації в асоціативний масив
$arrayTaxAdminData = \json_decode($fileContents, true);
// помилка?
if ($arrayTaxAdminData === FALSE) {
// зафіксовано помилку
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// помилка?
if ($erreur) {
// генерується виняток
throw new ExceptionImpots($message);
}
// ініціалізація атрибутів класу
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// перевіряється, чи всі ключі були ініціалізовані
$arrayOfAttributes = \get_object_vars($this);
foreach ($arrayOfAttributes as $key => $value) {
if (!isset($this->$key)) {
throw new ExceptionImpots("L'attribut [$key] de [TaxAdminData] n'a pas été initialisé");
}
}
// перевіряється, чи всі значення є дійсними
foreach ($this as $key => $value) {
// $value має бути дійсним числом >=0 або масивом дійсних чисел >=0
$result = $this->check($value);
// помилка?
if ($result->erreur) {
// генерується виняток
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// записується значення
$this->$key = $result->value;
}
}
// повертається об’єкт
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// JSON-рядок об’єкта
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// гетери та сеттери
public function getLimites() {
return $this->limites;
}
public function getCoeffR() {
return $this->coeffR;
}
…
}
public function setLimites($limites) {
$this->limites = $limites;
return $this;
}
public function setCoeffR($coeffR) {
$this->coeffR = $coeffR;
return $this;
}
…
}
Коментарі
- рядки 6–20: атрибути, які будуть містити атрибути з такими самими іменами з файлів jSON та [taxadmindata.json]. Це важливий момент: атрибути класу [TaxAdminData] ідентичні атрибутам файлів jSON та [taxadmindata.json]. Ця особливість значно полегшує написання коду;
- клас [TaxAdminData] не має конструктора. У PHP неможливо мати кілька конструкторів. Тому визначення одного конструктора унеможливлює ініціалізацію об’єкта іншим способом. Надалі наші класи не матимуть конструктора, але матимуть кілька методів типу [setFromQqChose], які дозволять ініціалізувати об’єкт різними способами. Створення об’єкта типу [TaxAdminData] здійснюється за допомогою виразу:
- рядок 23: метод [setFromJsonFile] ініціалізує атрибути класу значеннями з файлу [$jsonFilename], що мають такі самі імена;
- рядки 24–42: файл jSON використовується для побудови асоціативного масиву [$arrayTaxAdminData]. Ми вже зустрічали цей код у скрипті [main.php] версії 02;
- рядки 44–47: якщо під час обробки файлу jSON сталася помилка, генерується виняток. Він передається до головного скрипта [main.php];
- рядки 48–51: ініціалізуються атрибути класу. Тут використовується той факт, що асоціативний масив [$arrayTaxAdminData] та клас [TaxAdminData] мають атрибути з тими самими іменами, що й значення з файлу jSON;
- рядки 53–57: перевіряється, чи всі атрибути класу [TaxAdminData] були ініціалізовані;
- рядок 53: вираз [get_object_vars($this)] повертає асоціативний масив, атрибути якого є атрибутами об’єкта [$this], а отже, атрибутами класу [TaxAdminData]. Тут слід розуміти, що під час ініціалізації в рядках 48–51 до об’єкта [$this] могли бути додані атрибути. Отже, якщо написати:
то атрибут [x] додається до об’єкта [$this], навіть якщо цей атрибут не був оголошений у класі [TaxAdminData]. Безперечно, атрибути рядків 6–20 дійсно є частиною об’єкта [$this], але вони могли бути неініціалізовані. Цю помилку легко зробити — достатньо помилитися в імені атрибута у файлі [taxadmindata.json];
- рядки 54–57: виконується перегляд усіх атрибутів [$this], і якщо якийсь із них не було ініціалізовано, генерується виняток;
- атрибут може бути ініціалізований з неправильним значенням. У PHP неможливо вказати тип атрибутів. Тому операція:
є можливою, хоча атрибут [$plafondQfDemiPart] повинен бути дійсним числом;
- рядки 59–71: перевіряється, чи кожен з атрибутів класу має дійсне числове значення, яке є додатним або дорівнює нулю. Цю роботу виконує функція [check] у рядку 76. Її параметр [$value] є або єдиним значенням, або масивом значень;
- рядок 62: функція [check] повертає об’єкт типу [\stdClass] із двома атрибутами:
- [erreur]: при помилці — TRUE, в іншому випадку — FALSE;
- [value]: фактичне числове значення, що відповідає параметру [$value], переданому як аргумент, рядок 62;
- рядок 64: перевіряється, чи перевірка пройшла успішно;
- рядок 66: якщо атрибут не є дійсним додатним числом або нулем, генерується виняток;
- рядок 69: інакше записується його числове значення;
- рядок 73: повертається об’єкт [$this] як результат;
Функція [check] має такий вигляд:
private function check($value): \stdClass {
// $value — це або масив елементів, або окремий елемент
// створюється масив
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// масив елементів невідомого типу перетворюється на масив дійсних чисел
$newTableau = [];
$result = new \stdClass();
// елементи масиву мають бути додатними або нульовими десятковими числами
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// вставляємо число типу 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;
}
Коментарі
- рядок 1: параметр [$value] є або масивом, або окремим елементом. При цьому його тип невідомий. Значення береться з файлу [taxadmindata.json]. Залежно від значень, записаних у цьому файлі, зчитані значення можуть бути цілими числами, дійсними числами, рядками або логічними значеннями. Наприклад:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
У випадку 1 значення має тип [entier], у випадку 2 — тип [réel], у випадку 3 — типу [string], яке можна перетворити на число, у випадку 4 — типу [string], яке не можна перетворити на число;
- рядки 4–8: створюється масив на основі параметра [$value], отриманого як параметр у рядку 1;
- рядок 10: масив, який буде заповнено дійсними числами;
- рядок 11: результатом буде об’єкт типу [\stdClass];
- рядок 13: реляційний вираз, що визначає додатне або нульове дійсне число;
- рядки 14–24: перевіряємо, чи всі елементи масиву [$tableau] є додатними або нульовими дійсними числами, і заповнюємо масив [$newTableau] цими елементами, перетвореними у тип [float] (рядок 17);
- рядки 18–23: щойно виявляється елемент, який не є додатним дійсним числом або нулем, у результаті фіксується помилка, і цей результат повертається;
- рядки 25–34: випадок, коли всі елементи масиву [$tableau] визнано правильними;
- рядок 32: значення, що повертається [$result→value], є масивом дійсних чисел [float] або окремим дійсним числом;
Функція [__toString] у рядках 82–85 повертає рядок jSON з атрибутами та значеннями об’єкта [$this].
Рядки 87–110: методи getter та setter класу;
Примітка: іноді може бути дещо обтяжливо писати всі методи get/set для класу, особливо коли атрибутів багато. NetBeans може автоматично згенерувати їх, а також конструктор. Для цього просто вкажіть атрибути [1]:

- у [2], клацніть правою кнопкою миші в тому місці, куди хочете вставити код, а потім виберіть опцію [Insert Code];

- в [4] вкажіть, що ви хочете згенерувати конструктор;
- у [5], поставте галочки біля всіх атрибутів: це означає, що ви хочете, щоб конструктор мав параметр для кожного з атрибутів;
- у [6] — використовуйте стиль конструкторів Java;
- у [7] вкажіть, що ви явно хочете, щоб перед конструктором було ключове слово [public];
- у [8] натисніть «Підтвердити»;

- у [9] NetBeans згенерував конструктор. Однак він не зміг вказати типи параметрів, оскільки їх не знає. Додайте їх самостійно: [10];
Щоб згенерувати методи getter і setter, повторіть кроки 2–4, а на кроці 4 виберіть [Getter and Setter]:

- у [5] вкажіть, що вам потрібні методи getter і setter для кожного з атрибутів;
- у [6] вкажіть, що вам потрібні методи getter і setter у стилі, який використовується в Java: setAttribut, getAttribut;
- у [7] вкажіть, що ці методи getter та setter мають бути публічними;
- у [8] підтвердіть;

- у [9] — геттери та сеттери, згенеровані NetBeans;
Видаліть ці методи getter та setter і повторіть кроки 2–7.
- у [8] встановіть прапорець [Fluent Setter], який ми раніше не встановлювали;
Отриманий результат такий:

Кожен сеттер закінчується операцією [return $this]. Це дозволяє ініціалізувати атрибути наступним чином:
Дійсно, значення [$data→setLimites($limites)] (рядок 32 коду) дорівнює [$this], отже, тут — [$data]. Отже, можна викликати метод [setCoeffR($coeffR)] цього об’єкта і так далі, оскільки, у свою чергу, цей метод також повертає [$this] (рядок 37 коду). Такий спосіб написання методів класу, завдяки якому методи, що не повинні нічого повертати, повертають об’єкт [$this], називається «плавним записом». Він полегшує використання цих методів.
8.4. Інтерфейс [InterfaceImpots]
Тепер ми визначаємо наступний інтерфейс [InterfaceImpots] [InterfaceImpots.php]:
<?php
// простір імен
namespace Application;
interface InterfaceImpots {
// отримати дані про податкові групи, що дозволяють розрахувати податок
// може викликати виняток ExceptionImpots
public function getTaxAdminData(): TaxAdminData;
// інтерфейс вміє розраховувати податок
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// інтерфейс вміє обробляти дані з текстових файлів
// $usersFilename: файл даних користувача у вигляді сімейного стану, кількості дітей, річної заробітної плати
// $resultsFilename: файл результатів у вигляді сімейного стану, кількості дітей, річної заробітної плати, суми податку
// $errorsFilename: файл виявлених помилок
// може викликати виняток ExceptionImpots
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Коментарі
- рядок 4: інтерфейс розміщений у просторі імен [Application];
- рядок 6: інтерфейс, що дозволяє розраховувати податки;
- рядок 10: метод [getTaxAdminData] дозволить отримати дані від податкової адміністрації в об’єкт типу [TaxAdminData], який ми щойно представили. Оскільки ці дані можуть міститися у файлі, базі даних або навіть у мережі, метод [getTaxAdminData] може не змогти отримати дані. У цьому випадку він викличе виняток типу [ExceptionImpots]. Це стандартний метод в об’єктному програмуванні для сигналізації про помилку, що виникла в методі або конструкторі;
- рядок 13: метод [calculerImpot] дозволить розрахувати податок для одного користувача;
- рядок 20: метод [executeBatchImpots] дозволить обчислити податок для кількох платників податків:
- [$usersFileName] — це ім’я текстового файлу, що містить дані про платників податків;
- [$resultsFileName] — назва текстового файлу, що містить суму податку для цих платників податків;
- [$errorsFileName] — назва текстового файлу, що містить помилки, виявлені під час обробки цих файлів;
Зміст текстового файлу [$usersFileName] може бути таким:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000
Зверніть увагу, що рядки 5 та 7 містять помилкові елементи.
Тоді вміст текстового файлу [$resultsFileName] буде таким:
а вміст текстового файлу [$errorsFileName] — таким:
8.5. Клас [Utilitaires]
Крім того, ми визначаємо клас [Utilitaires] у файлі [Utilitaires.php]:
<?php
// простір імен
namespace Application;
// клас допоміжних функцій
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// видаляється маркер кінця рядка з $ligne, якщо він існує
$longueur = strlen($ligne); // довжина рядка
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// кінець — повертаємо рядок
return($ligne);
}
}
Коментарі
- рядок 4: клас [Utilitaires] також розміщений у просторі імен [Exemples];
- рядок 9: метод [cutNewLinechar] видаляє можливий символ кінця рядка з тексту, переданого йому як параметр. Він повертає новий рядок, сформований таким чином. Зазначимо, що це статичний метод, тобто він викликатиметься у формі [Utilitaires::cutNewLineChar];
8.6. Абстрактний клас [AbstractBaseImpots]
Інтерфейс [InterfaceImpots] буде реалізовано наступним абстрактним класом [AbstractBaseImpots]: [AbstractBaseImpots.php]:
<?php
// простір імен
namespace Application;
// визначення абстрактного класу AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// дані податкової адміністрації
private $taxAdminData = NULL;
// дані, необхідні для розрахунку податку
abstract function getTaxAdminData(): TaxAdminData;
// розрахунок податку
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié: так, ні
// $enfants: кількість дітей
// $salaire: річний оклад
// $this->taxAdminData: дані податкової служби
//
// перевіряємо, чи є дані податкової служби
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// розрахунок податку з урахуванням дітей
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// розрахунок податку без урахування дітей
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// застосування обмеження сімейного коефіцієнта
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART євро за перших двох дітей
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART євро за перших двох дітей, удвічі більше за наступних
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// береться найвищий податок
if ($impot1 > $impot2) {
$impot = $impot1;
$taux = $result1["taux"];
$surcôte = $result1["surcôte"];
} else {
$surcôte = $impot2 - $impot1 + $result2["surcôte"];
$impot = $impot2;
$taux = $result2["taux"];
}
// розрахунок можливої знижки
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// розрахунок можливого зменшення податків
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// результат
return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
// --------------------------------------------------------------------------
private function calculerImpot2(string $marié, int $enfants, float $salaire): array {
…
// результат
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=річна зарплата-пільга
// податкова пільга має мінімальний та максимальний розмір
private function getRevenuImposable(float $salaire): float {
…
// результат
return floor($revenuImposable);
}
// розраховує можливе зниження
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// результат
return ceil($décôte);
}
// розраховує можливе зменшення
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// результат
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
Коментарі
- рядок 4: клас [AbstractBaseImpots] буде знаходитися в просторі імен [Application], як і інші елементи програми, що зараз пишеться;
- рядок 7: клас [AbstractBaseImpots] реалізує інтерфейс [InterfaceImpots];
- рядок 9: дані податкової адміністрації будуть розміщені в атрибуті [$taxAdminData];
- рядок 12: реалізація методу [getTaxAdminData] інтерфейсу. Поки що ми не знаємо, як визначити цей метод: у попередньому абзаці ми розглянули приклад, де дані податкової служби були взяті з файлу jSON. Ми розглянемо інший випадок, коли дані потрібно буде шукати в базі даних. Визначення змісту методу [getTaxAdminData] буде покладено на похідні класи. Два попередні випадки дадуть початок двом похідним класам. Отже, метод [getTaxAdminData] оголошено абстрактним, що автоматично робить сам клас абстрактним (рядок 7);
- рядки 15–64: функція розрахунку податку, з якою ми вже знайомилися в параграфах посилання та посилання;
- у версії 02 дані податкової адміністрації розміщувалися в асоціативному масиві [$taxAdminData]. У версії 03 вони розміщуються в атрибуті [$this→taxAdminData]. Перша відмінність між цими двома рішеннями полягає у різниці в видимості податкових даних:
- у версії 02 асоціативний масив [$taxAdminData] не мав глобальної видимості. Тому він передавався як параметр усім функціям розрахунку податку;
- у версії 03 атрибут [$this→taxAdminData] має глобальну видимість для всіх методів класу. Отже, він не передається як параметр усім функціям розрахунку податку;
- друга відмінність полягає в тому, що у версії 03 функції замінено на методи класу. Кожен виклик методу тепер здійснюється за допомогою виразу [$this→getMéthode(…)] (рядки 27, 31, 57, 60);
- третя відмінність полягає в тому, що коли метод [calculerImpot] починає свою роботу, він не знає, чи було ініціалізовано атрибут [private $taxAdminData], який йому потрібен. Адже конструктор класу його не ініціалізує. Тому метод [calculerImpot] повинен зробити це за допомогою методу [getTaxAdminData] у рядку 12. Саме це робиться в рядках 23–25;
- крім цих відмінностей, методи розрахунку податку залишаються такими ж, як і в попередніх версіях;
Функція [executeBatchImpots] має такий вигляд:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// під час роботи з файлами може виникнути чимало помилок
try {
// помилки під час відкриття файлу
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// відкриття файлу результатів
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// читання даних користувача
// кожен рядок має такий формат: сімейний стан, кількість дітей, річний оклад
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// обробляється поточний рядок файлу даних користувача
// яка має такий формат: сімейний стан, кількість дітей, річний оклад
$num = 1; // номер поточного рядка
$nbErreurs = 0; // кількість виявлених помилок
while ($ligne = fgets($data, 100)) {
// debug
// print "рядок № " . ($i + 1) . " : " . $ligne;
// видаляємо можливий символ кінця рядка
$ligne = Utilitaires::cutNewLineChar($ligne);
// отримуємо 3 поля «одружений:діти:зарплата», які утворюють $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// перевіряємо їх
// сімейний стан має бути «так» або «ні»
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// кількість дітей має бути цілим числом
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// зарплата — ціле число без центів
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// помилка?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// розраховується податок
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// результат записується у файл результатів
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// наступний рядок
$num++;
}
// помилки?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// повторно генерується виняток
throw $ex;
} finally {
// закриваємо всі файли
fclose($data);
fclose($results);
fclose($errors);
}
}
Коментарі до коду
- рядок 1: функція приймає три параметри:
- [$usersFileName]: назва текстового файлу, що містить дані про платників податків. Кожен рядок тексту містить дані про одного платника податків у такому форматі: сімейний стан (так / ні), кількість дітей, річний заробіток:
- (продовження)
- [$resultsFileName]: ім’я текстового файлу, що міститиме результати. Кожен рядок тексту матиме такий вигляд:
- (продовження)
- [$errorsFileName]: ім’я текстового файлу з помилками:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- рядок 3: оскільки певна кількість операцій може викликати виняток, весь код методу оточений блоком try / catch / finally;
- рядки 3–19: відкриваються три файли. Як тільки відкриття файлу завершується невдало, генерується виняток;
- рядок 24: рядки файлу [$data] зчитуються по одному, по 100 символів максимум (усі рядки містять менше 100 символів);
- рядок 28: використовується статичний метод [Utilitaires::cutNewLineChar] для видалення можливого символу кінця рядка;
- рядок 30: витягуються три елементи з прочитаного рядка;
- рядки 33–52: перевіряється правильність трьох елементів. У цьому випадку, якщо сталася помилка, виняток не генерується, а повідомлення про неї записується у текстовий файл [$errors] (рядок 55);
- рядок 59: якщо прочитаний рядок є дійсним, виконується розрахунок податку. Отримуємо результат у вигляді асоціативного масиву ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
- рядок 61: до отриманого результату додаються ключі [marié, enfants, salaire];
- рядок 61: результат записується у текстовий файл [$results] у вигляді рядка jSON, що містить отриманий результат;
- рядки 68–70: після завершення обробки файлу [$data] перевіряється кількість виявлених помилкових рядків. Якщо їх хоча б один, генерується виняток;
- рядки 71–74: перехоплюється виняток, який міг бути згенерований кодом, і негайно перекидається (рядок 73). Мета цього прийому — забезпечити наявність блоку [finally] у рядках 74–79: незалежно від того, як завершиться виконання коду методу, три файли, які могли бути відкриті цим кодом, закриваються. Закриття файлу, який не було відкрито, не спричиняє помилки;
8.7. Клас [ImpotsWithTaxAdminDataInJsonFile]
Абстрактний клас [AbstractBaseImpots] не реалізує метод [getTaxAdminData] інтерфейсу [InterfaceImpots]. Тому нам потрібно визначити його в похідному класі. Ми робимо це в наступному похідному класі [ImpotsWithTaxAdminDataInJsonFile]:
<?php
// простір імен
namespace Application;
// визначення класу ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// атрибут типу Data
private $taxAdminData;
// конструктор
public function __construct(string $jsonFileName) {
// ініціалізується $this->taxAdminData на основі файлу jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// повертає дані, необхідні для розрахунку податку
public function getTaxAdminData(): TaxAdminData {
// повертає атрибут [$this->taxAdminData]
return $this->taxAdminData;
}
}
Коментарі
- рядок 7: клас [ImpotsWithTaxAdminDataInJsonFile] успадковує абстрактний клас [AbstractBaseImpots]. У ньому потрібно буде визначити метод [getTaxAdminData], який не був визначений у його батьківському класі;
- рядок 9: атрибут [$taxAdminData] міститиме дані податкової адміністрації;
- рядки 12–15: конструктор отримує як єдиний параметр ім’я файлу jSON, що містить податкові дані;
- рядок 14: створюється та ініціалізується об’єкт типу [TaxAdminData]. Ця операція може викликати виняток типу [ExceptionImpots]. Цей виняток буде передано до головного скрипта [main.php];
- рядки 18–20: задається тіло методу [getTaxAdminData], яке не було визначено батьківським класом. Тут достатньо зробити так, щоб атрибут [$this->taxAdminData] ініціалізувався конструктором;
8.8. Скрипт [main.php]
Ці класи та інтерфейс використовуються у наступному скрипті [main.php]:
<?php
// суворе дотримання оголошених типів параметрів функцій
declare(strict_types = 1);
// простір імен
namespace Application;
// включення інтерфейсів та класів
require_once __DIR__ . '/InterfaceImpots.php';
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . '/ExceptionImpots.php';
require_once __DIR__ . '/Utilitaires.php';
require_once __DIR__ . '/AbstractBaseImpots.php';
require_once __DIR__ . "/ImpotsWithTaxAdminDataInJsonFile.php";
// тест -----------------------------------------------------
// визначення констант
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// створюємо об’єкт ImpotsWithTaxAdminDataInJsonFile
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// виконується пакетна обробка податків
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// виведення повідомлення про помилку
print $ex->getMessage() . "\n";
}
// кінець
print "Terminé\n";
exit();
Коментарі
- рядок 4: встановлюється суворе дотримання типів параметрів функцій;
- рядок 7: скрипт [main.php] також розміщений у просторі імен [Application];
- рядки 10–15: тут вказується інтерпретатору PHP, де знаходяться класи та інтерфейси, що використовуються скриптом. Зауважимо, що тут ми не використовували інструкцію use для оголошення повних імен класів, що використовуються скриптом. Це дійсно непотрібно, оскільки скрипт і класи знаходяться в одному просторі імен [Application];
- рядки 18–22: імена текстових файлів, що використовуються у скрипті;
- рядки 24–29: створюється об’єкт [ImpotsWithTaxAdminDataInJsonFile] та обробляється можливе виключення;
- рядок 28: виконується метод [executeBatchImpots], який обчислить податки для всіх платників податків із файлу [TAXPAYERSDATA_FILENAME]. Результати будуть записані у файл [RESULTS_FILENAME], а можливі помилки — у файл [ERRORS_FILENAME];
- рядки 29–32: у разі виникнення непереборної помилки виводиться повідомлення про помилку;
Результати
З файлом платників податків [taxpayersdata.txt], що міститься нижче:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000
отримуємо такий файл помилок [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
та такий файл результатів [resultats.txt]: