Skip to content

13. Ćwiczenie praktyczne – wersja 5

Image

Napisaliśmy już kilka wersji tego ćwiczenia. W ostatniej wersji zastosowano architekturę warstwową:

Image

Warstwa [dao] implementuje interfejs [InterfaceDao]. Stworzyliśmy klasę implementującą ten interfejs:

  • [DaoImpotsWithTaxAdminDataInJsonFile], która pobierała dane podatkowe z pliku jSON;

Zaimplementujemy interfejs [InterfaceDao] za pomocą nowej klasy [DaoImpotsWithTaxAdminDataInDatabase], która będzie pobierać dane z urzędu skarbowego z bazy danych MySQL.

13.1. Tworzenie bazy danych [dbimpots-2019]

Kierując się przykładem z akapitu „link”, tworzymy bazę danych o nazwie MySQL, nazwaną [dbimpots-2019], której właścicielem będzie [admimpots], a hasło to [mdpimpots]:

Image

  • w powyższym [1-4] widzimy bazę [dbimpots-2019], która na razie nie zawiera żadnych tabel;

Image

  • w [1-5] powyżej widzimy, że użytkownik [admimpots] ma pełne uprawnienia do bazy [dbimpots-2019]. Nie widać tu jednak, że użytkownik ten ma hasło [admimpots];

Teraz tworzymy tabelę [tbtranches], która będzie zawierała przedziały podatkowe:

Image

  • w [1-7] tworzymy tabelę o nazwie [tbtranches] zawierającą 4 kolumny;

Image

  • w [3-6] definiujemy kolumnę o nazwie [id] (3) typu całkowitoliczbowego [int] (4), która będzie kluczem głównym [6] tej tabeli i będzie automatycznie zwiększana [5] przez SGBD. Oznacza to, że MySQL sam będzie zarządzał wartościami klucza głównego podczas wstawiania danych. Przypisze wartość 1 do klucza głównego pierwszego wstawionego rekordu, następnie 2 do następnego itd.;
  • w przypadku [7] kreator proponuje nam inne opcje konfiguracji klucza głównego. W tym przypadku wystarczy zatwierdzić wartości domyślne w [7];

Image

  • W polu [8-16] definiuje się pozostałe trzy kolumny tabeli:
    • [limites] (8) typu liczba dziesiętna (9) o długości 10 cyfr, w tym 2 miejsca po przecinku (10), będzie zawierać elementy kolumny 17 dotyczącej przedziałów podatkowych;
    • [coeffR] (11) typu liczba dziesiętna (12) o długości 6 cyfr, w tym 2 miejsca po przecinku (13), będzie zawierać elementy kolumny 18 przedziałów podatkowych;
    • [coeffN] (14) typu liczba dziesiętna (15) składająca się z 10 cyfr, w tym 2 miejsc po przecinku (16), będzie zawierać elementy z kolumny 19 przedziałów podatkowych;

Po zatwierdzeniu tej struktury otrzymujemy następujący wynik:

Image

  • w [5] ikona klucza wskazuje, że kolumna [id] jest kluczem głównym. Widać również, że ten klucz główny ma wartości całkowite (6) i jest zarządzany (autoinkrementowany) przez MySQL;

W taki sam sposób, w jaki utworzyliśmy tabelę [tbtranches], tworzymy tabelę [tbconstantes], która będzie zawierała stałe potrzebne do obliczenia podatku:

Image

Możliwe jest wyeksportowanie struktury bazy danych do pliku tekstowego w postaci sekwencji poleceń SQL:

Image

Opcja [5] eksportuje w tym przypadku wyłącznie strukturę bazy danych, a nie jej zawartość. W naszym przypadku baza nie zawiera jeszcze żadnych danych.

Image

Image

Image

Opcja [11] generuje następujący plik SQL [dbimpots-2019.sql]:


-- phpMyAdmin SQL Zrzut
-- wersja 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Czas wygenerowania: 30 czerwca 2019 r. o godz. 01:10 PM
-- Wersja serwera: 5.7.24
-- PHP Wersja: 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 */;

--
-- Baza danych: `dbimpots-2019`
--
CREATE DATABASE IF NOT EXISTS `dbimpots-2019` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `dbimpots-2019`;

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

--
-- Struktura tabeli `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;

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

--
-- Struktura tabeli `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;

--
-- Indeksy dla zrzuconych tabel
--

--
-- Indeksy dla tabeli `tbconstantes`
--
ALTER TABLE `tbconstantes`
  ADD PRIMARY KEY (`id`);

--
-- Indeksy dla tabeli `tbtranches`
--
ALTER TABLE `tbtranches`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT dla zrzuconych tabel
--

--
-- AUTO_INCREMENT dla tabeli `tbconstantes`
--
ALTER TABLE `tbconstantes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT dla tabeli `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 */;

Plik ten można wykorzystać do odtworzenia bazy danych, jeśli została ona usunięta lub uszkodzona. Nie ma potrzeby usuwania bazy przed jej odtworzeniem, ponieważ skrypt SQL sam się tym zajmie:

Image

Image

13.2. Organizacja kodu

Aby lepiej zilustrować rolę poszczególnych skryptów PHP, które piszemy, uporządkujemy nasz kod w folderach:

Image

  • w [1] – przegląd wersji 05;
  • w [2] – elementy aplikacji, elementy wymieniane między warstwami;
  • w [3] – narzędzia aplikacji;
  • w pliku [4] – dane wykorzystywane lub generowane przez aplikację. Podejmujemy tutaj decyzję o stosowaniu wyłącznie plików jSON dla plików tekstowych. Pliki te mają kilka zalet:
    • są rozpoznawane przez wiele narzędzi;
    • narzędzia te oferują podświetlanie składni. Ponadto notacja jSON podlega określonym regułom. Gdy nie są one przestrzegane, narzędzia sygnalizują ten fakt. Na przykład błędem trudnym do wykrycia w prostym pliku tekstowym jest użycie wielkiej lub małej litery „O” zamiast zer. Jeśli ten błąd wystąpi, zostanie zgłoszony. W kodzie jSON:

„plafondRevenusCouplePourReduction”: 42O74

gdzie w [42074] przez pomyłkę umieszczono wielką literę „O” zamiast zera, NetBeans zgłasza błąd:

Image

Rzeczywiście, NetBeans rozpoznaje wielką literę „O”, która sprawia, że [49O74] jest ciągiem znaków. Na tej podstawie wnioskuje, że poprawna składnia powinna brzmieć [4-5]: ciąg znaków [47O74] powinien być ujęty w cudzysłowy. W ten sposób programista zostaje zwrócony na ten błąd i może go poprawić: albo dodając cudzysłowy, albo zastępując literę „O” cyfrą zero;

Pozostałe elementy wersji 05 są następujące:

Image

  • w [6] – interfejsy i klasy warstwy [Dao];
  • z [7] na interfejsy i klasy warstwy [métier];
  • w [8] – główne skrypty wersji 05;

Wersja 05 ma dwa odrębne cele:

  • wypełnić bazę danych MySQL [dbimpots-2019] treścią pliku jSON [Data/txadmindata.json];
  • wdrożyć obliczanie podatku przy użyciu danych podatkowych pochodzących odtąd z bazy MySQL [dbimpots-2019];

Omówimy te dwa cele osobno.

13.3. Wypełnianie bazy danych [dbimpots-2019]

13.3.1. Cel

Plik tekstowy taxadmindata.json zawiera dane z urzędu skarbowego:


{
    "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
}

Naszym celem jest przeniesienie tych danych do utworzonej wcześniej bazy danych MySQL [dbimpots-2019].

13.3.2. Elementy

Image

Jednostka [Database] posłuży do zawarcia danych z następującego pliku 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"
}

Entyteta [TaxAdminData] służy do enkapsulacji danych z następujących plików: jSON i [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
}

Entity [TaxPayerData] służy do enkapsulacji danych z następujących plików: jSON i [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. Klasa bazowa [BaseEntity]

Aby uprościć kod encji, zastosujemy następującą zasadę: atrybuty encji mają takie same nazwy jak atrybuty pliku jSON, który encja ma enkapsulować. Zgodnie z tą zasadą jednostki [Database, TaxAdminData, TaxPayerData] mają cechy wspólne, które można wyodrębnić do klasy nadrzędnej. Będzie to następująca klasa [BaseEntity]:


<?php

namespace Application;

class BaseEntity {
  // atrybut
  protected $arrayOfAttributes;

  // inicjalizacja z pliku jSON
  public function setFromJsonFile(string $jsonFilename) {
    // pobieramy zawartość pliku z danymi podatkowymi
    $fileContents = \file_get_contents($jsonFilename);
    $erreur = FALSE;
    // błąd?
    if (!$fileContents) {
      // odnotowuje się błąd
      $erreur = TRUE;
      $message = "Le fichier des données [$jsonFilename] n'existe pas";
    }
    if (!$erreur) {
      // pobieramy kod jSON z pliku konfiguracyjnego do tablicy asocjacyjnej
      $this->arrayOfAttributes = \json_decode($fileContents, true);
      // błąd?
      if ($this->arrayOfAttributes === FALSE) {
        // odnotowuje się błąd
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
      }
    }
    // błąd?
    if ($erreur) {
      // rzucamy wyjątek
      throw new ExceptionImpots($message);
    }
    // inicjalizacja atrybutów klasy
    foreach ($this->arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // zwracamy obiekt
    return $this;
  }

  public function checkForAllAttributes() {
    // sprawdzamy, czy wszystkie klucze zostały zainicjowane
    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) {
    // inicjowanie niektórych atrybutów klasy
    foreach ($arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // zwracamy obiekt
    return $this;
  }

  // toString
  public function __toString() {
    // atrybuty obiektu
    $arrayOfAttributes = \get_object_vars($this);
    // usuwamy atrybut z klasy nadrzędnej
    unset($arrayOfAttributes["arrayOfAttributes"]);
    // ciąg JSON obiektu
    return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
  }

  // metoda pobierająca
  public function getArrayOfAttributes() {
    return $this->arrayOfAttributes;
  }

}

Komentarze

  • wiersz 5: klasa [BaseEntity] jest przeznaczona do rozszerzenia przez klasy [Database, TaxAdminData, TaxPayerData];
  • wiersz 7: atrybut [$arrayOfAttributes] jest tablicą zawierającą wszystkie atrybuty klasy potomnej, która rozszerzyła klasę [BaseEntity], wraz z ich wartościami;
  • wiersze 9–41: atrybut [$arrayOfAttributes] jest inicjowany na podstawie pliku jSON [$jsonFilename] przekazanego jako parametr. Wyzwalany jest wyjątek typu [ExceptionImpot], jeśli nie udało się odczytać pliku jSON lub jeśli nie jest to prawidłowy plik jSON;
  • wiersze 36–38: jest to specjalny kod, jeśli jest wykonywany przez klasę potomną. W tym przypadku [$this] reprezentuje instancję klasy potomnej [Database, TaxAdminData, TaxPayerData], a wówczas wiersze 36–38 inicjalizują atrybuty tej klasy potomnej, pod warunkiem że atrybuty te mają widoczność protected (lub public) (zob. akapit „link”). Stwierdzono bowiem, że atrybuty encji [Database, TaxAdminData, TaxPayerData] są takie same jak atrybuty pliku jSON, który te encje enkapsulują. Wreszcie metoda [setFromJsonFile] umożliwia klasie potomnej zainicjowanie się na podstawie pliku jSON;
  • wiersz 40: zwracamy obiekt [$this], czyli instancję klasy potomnej, jeśli metoda [setFromJsonFile] została wywołana przez klasę potomną;
  • wiersze 43–51: metoda [checkForAllAttributes] pozwala klasie potomnej sprawdzić, czy wszystkie jej atrybuty zostały zainicjowane. Jeśli tak nie jest, zgłaszany jest wyjątek [ExceptionImpots]. Metoda ta pozwala klasie potomnej sprawdzić, czy w jej pliku jSON nie pominięto niektórych atrybutów;
  • wiersze 53–60: metoda [setFromArrayOfAttributes] pozwala klasie potomnej zainicjować wszystkie lub część swoich atrybutów na podstawie tablicy asocjacyjnej, której klucze mają takie same nazwy jak atrybuty klasy potomnej, która ma zostać zainicjowana;
  • wiersze 63–70: metoda [__toString] pozwala uzyskać reprezentację jSON klasy potomnej;

13.3.2.2. Entyteta [Database]

Entyteta [Database] ma następujący wygląd:


<?php

namespace Application;

class Database extends BaseEntity {
  // atrybuty
  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;



}

Klasa [Database] służy do enkapsulacji danych z następującego pliku 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"
}

Klasa i plik jSON mają te same atrybuty. Opisują one cechy bazy danych MySQL [dbimpots-2019]:

dsn
Nazwa bazy danych DSN
id
Właściciel bazy
pwd
Jego hasło
tableTranches
Nazwa tabeli zawierającej przedziały podatkowe
colLimites
colCoeffR
colCoeffN
Nazwy kolumn tabeli [tableTranches]
tableConstantes
Nazwa tabeli zawierającej stałe służące do obliczania podatku
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
Nazwy kolumn tabeli [tableConstantes] zawierającej stałe służące do obliczania podatku

Po co nazywać tabele i kolumny, skoro znamy już ich nazwy i nie ulegną one zmianie? Po tabelach SGBD i MySQL będziemy używać tabel SGBD i PostgreSQL do przechowywania danych administracji podatkowej. Jednak nazwy kolumn i tabel w Postgresie nie są zgodne z tymi samymi zasadami, co w przypadku MySQL. Będziemy zmuszeni użyć innych nazw. Dotyczy to również innych tabel typu SGBD. Jeśli chcemy, aby kod był przenośny między tabelami typu SGBD, lepiej jest używać parametrów zamiast stałych nazw tabel i kolumn.

Wróćmy do kodu klasy [Database]:


<?php

namespace Application;

class Database extends BaseEntity {
  // atrybuty
  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;

  // metoda setter
  // inicjalizacja
  public function setFromJsonFile(string $jsonFilename) {
    // element nadrzędny
    parent::setFromJsonFile($jsonFilename);
    // sprawdzamy, czy wszystkie atrybuty zostały zainicjowane
    parent::checkForAllAttributes();
    // zwracamy obiekt
    return $this;
  }

  // metody pobierające i ustawiające
  public function getDsn() {
    return $this->dsn;
  }



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



}

Komentarze

  • wiersze 7–24: wszystkie atrybuty klasy mają widoczność [protected]. Jest to warunek konieczny, aby można je było modyfikować z poziomu klasy nadrzędnej [BaseEntity] (patrz akapit dotyczący linków);
  • wiersze 28–35: metoda [setFromJsonFile] pozwala zainicjować atrybuty klasy [Database] na podstawie zawartości pliku jSON przekazanego jako parametr. Atrybuty pliku jSON i klasy [Database] muszą być identyczne. Jeśli plik jSON nie nadaje się do wykorzystania, zgłaszany jest wyjątek;
  • wiersz 30: to klasa nadrzędna dokonuje inicjalizacji;
  • wiersz 32: prosimy klasę nadrzędną o sprawdzenie, czy wszystkie atrybuty klasy [Database] zostały zainicjowane. Jeśli tak nie jest, zgłaszany jest wyjątek;
  • wiersz 34: zwracana jest właśnie zainicjowana instancja klasy [Database];
  • wiersze 37 i kolejne: metody getter i setter atrybutów klasy;

13.3.2.3. Entyteta [TaxAdminData]

Entity [TaxAdminData] ma następującą postać:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // przedziały podatkowe
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // stałe służące do obliczania podatku
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  
}

Klasa [TaxAdminData] służy do enkapsulacji danych z następujących plików: jSON i [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
}

Klasa i plik jSON mają te same atrybuty. Reprezentują one dane administracji podatkowej. Pozostała część kodu klasy [TaxAdminData] wygląda następująco:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // progi podatkowe
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // stałe do obliczania podatku
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  // inicjalizacja
  public function setFromJsonFile(string $taxAdminDataFilename) {
    // element nadrzędny
    parent::setFromJsonFile($taxAdminDataFilename);
    // sprawdzamy, czy wszystkie atrybuty zostały zainicjowane
    parent::checkForAllAttributes();
    // sprawdzamy, czy wartości atrybutów są liczbami rzeczywistymi >=0
    foreach ($this as $key => $value) {
      if ($key !== "arrayOfAttributes") {
        // $value musi być liczbą rzeczywistą >=0 lub tablicą liczb rzeczywistych >=0
        $result = $this->check($value);
        // błąd?
        if ($result->erreur) {
          // wyzwalany jest wyjątek
          throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
        } else {
          // zapisuje się wartość
          $this->$key = $result->value;
        }
      }
    }
    // zwracamy obiekt
    return $this;
  }

  protected function check($value): \stdClass {
    // $value jest tablicą elementów typu string lub pojedynczym elementem
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // przekształca się tablicę ciągów znaków w tablicę liczb rzeczywistych
    $newTableau = [];
    $result = new \stdClass();
    // elementy tablicy muszą być dodatnimi liczbami dziesiętnymi lub zerami
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        // umieszczamy liczbę zmiennoprzecinkową w newTableau
        $newTableau[] = (float) $tableau[$i];
      } else {
        // odnotowuje się błąd
        $result->erreur = TRUE;
        // kończymy
        return $result;
      }
    }
    // zwracamy wynik
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      // pojedyncza wartość
      $result->value = $newTableau[0];
    } else {
      // lista wartości
      $result->value = $newTableau;
    }
    return $result;
  }

  // metody pobierające i ustawiające

}

Komentarze

  • wiersz 23: metoda [setFromJsonFile] służy do zainicjowania atrybutów klasy [TaxAdminData] na podstawie pliku jSON przekazanego jako parametr. Atrybuty pliku jSON muszą istnieć pod tą samą nazwą w klasie;
  • wiersz 25: zadanie to wykonuje klasa nadrzędna;
  • wiersz 27: prosimy klasę nadrzędną o sprawdzenie, czy wszystkie atrybuty klasy podrzędnej zostały zainicjowane;
  • wiersze 29–42: lokalnie sprawdzamy, czy wszystkie atrybuty mają wartość rzeczywistą dodatnią lub zerową. Ta weryfikacja została już omówiona w akapicie dotyczącym linków w wersji 03;

13.3.3. Warstwa [dao]

Teraz możemy napisać kod, który przeniesie dane z pliku tekstowego [taxadmindata.json] do tabel [tbtranches, tbconstantes] w bazie danych MySQL [dbimpots-2019]. Przyjmiemy następującą architekturę:

Image

Image

Warstwa [dao] będzie implementować następujący interfejs [InterfaceDao4TransferAdminDataFromFile2Database]:


<?php

// przestrzeń nazw
namespace Application;

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

Komentarze

  • wiersz 8: metoda [transferAdminData2Database] służy do przechowywania danych administracji podatkowej w bazie danych;

Interfejs [InterfaceDao4TransferAdminData2Database] zostanie zaimplementowany przez następującą klasę [DaoTransferAdminDataFromJsonFile2Database]:


<?php

// przestrzeń nazw
namespace Application;

// definicja klasy TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
  // atrybuty docelowej bazy danych
  private $database;
  // dane urzędu skarbowego
  private $taxAdminData;

  // producent
  public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
    // zapisywana jest konfiguracja bazy danych
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // zapisywanie danych podatkowych
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // przenosi dane dotyczące przedziałów podatkowych z pliku tekstowego
  // do bazy danych
  public function transferAdminData2Database(): void {
    // praca z bazą danych
    $database = $this->database;
    try {
      // otwieramy połączenie z bazą danych
      $connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
      // chcemy, aby przy każdym błędzie SGBD zgłaszany był wyjątek
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // rozpoczynamy transakcję
      $connexion->beginTransaction();
      // wypełniamy tabelę przedziałów podatkowych
      $this->fillTableTranches($connexion);
      // wypełnia się tabelę stałych
      $this->fillTableConstantes($connexion);
      // transakcja zostaje pomyślnie zakończona
      $connexion->commit();
    } catch (\PDOException $ex) {
      // czy trwa jakaś transakcja?
      if (isset($connexion) && $connexion->inTransaction()) {
        // zakończenie transakcji z powodu niepowodzenia
        $connexion->rollBack();
      }
      // przekazujemy wyjątek do kodu wywołującego
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // zamyka się połączenie
      $connexion = NULL;
    }
  }


  // wypełnianie tabeli przedziałów podatkowych
  private function fillTableTranches($connexion): void {

  }

  // wypełnianie tabeli stałych
  private function fillTableConstantes($connexion): void {

  }

}

Komentarze

Wykorzystujemy tutaj wiedzę zdobytą w rozdziale poświęconym klasie MySQL.

  • wiersz 7: klasa [DaoTransferAdminDataFromJsonFile2Database] implementuje interfejs [InterfaceDao4TransferAdminData2Database];
  • wiersz 9: atrybut [$database] jest obiektem typu [Database], który zawiera dane z pliku [database.json];
  • wiersz 11: atrybut [$taxAdminData] jest obiektem typu [TaxAdminData], zawierającym dane z pliku [taxadmindata.json];
  • wiersze 14–19: konstruktor otrzymuje jako parametry nazwy plików [database.json, taxadmindata.json];
  • wiersz 16: inicjalizacja atrybutu [$database];
  • wiersz 18: inicjalizacja atrybutu [$taxAdminData];
  • wiersz 23: implementacja jedynej metody interfejsu [InterfaceDao4TransferAdminData2Database];
  • wiersze 26–38: wypełnianie tabeli [tbtranches, tbconstantes] w dwóch etapach:
    • wiersz 34: najpierw wypełnia się tabelę [tbtranches]. Odbywa się to w ramach transakcji (wiersze 32, 38). Metoda [fillTableTranches] (wiersz 55) zgłasza wyjątek, gdy tylko coś pójdzie nie tak. W takim przypadku wykonanie jest kontynuowane z blokiem catch / finally w wierszach 39–50;
    • wiersz 36: tabelę [tbconstantes] wypełnia się w ten sam sposób za pomocą metody [fillTableConstantes] (wiersz 60);
  • wiersze 39–47: sytuacja, w której kod wygenerował wyjątek;
  • wiersze 41–44: jeśli istnieje transakcja, zostaje ona anulowana;
  • wiersz 46: generowany jest wyjątek typu [ExceptionImpots] wraz z komunikatem pierwotnego wyjątku, który sam w sobie może być dowolnego typu;
  • wiersze 47–50: w klauzuli [finally] połączenie zostaje zamknięte;

Kod metody [fillTableTranches] jest następujący:


private function fillTableTranches($connexion): void {
    // skrót do bazy danych
    $database = $this->database;
    // dane do wstawienia do bazy danych
    $limites = $this->taxAdminData->getLimites();
    $coeffR = $this->taxAdminData->getCoeffR();
    $coeffN = $this->taxAdminData->getCoeffN();
    // opróżnianie tabeli na wypadek, gdyby coś się w niej znajdowało
    $statement = $connexion->prepare("delete from " . $database->getTableTranches());
    $statement->execute();
    // przygotowujemy operacje wstawiania
    $sqlInsert = "insert into {$database->getTableTranches()} "
      . "({$database->getColLimites()}, {$database->getColCoeffR()},"
      . " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
    $statement = $connexion->prepare($sqlInsert);
    // wykonujemy przygotowane polecenie z wartościami przedziałów podatkowych
    for ($i = 0; $i < count($limites); $i++) {
      $statement->execute([
        "limites" => $limites[$i],
        "coeffR" => $coeffR[$i],
        "coeffN" => $coeffN[$i]]);
    }
  }

Komentarze

  • wiersz 1: metoda [fillTableTranches] otrzymuje jako parametr otwarte połączenie. Wiadomo ponadto, że w ramach tego połączenia rozpoczęła się transakcja;
  • wiersze 5–7: wartości do wstawienia do tabeli są dostarczane przez atrybut [$taxAdminData];
  • wiersze 9–10: usuwa się aktualną zawartość tabeli [tbtranches];
  • wiersze 12–15: przygotowuje się wstawianie wierszy do tabeli. Wykorzystuje się tutaj nazwy kolumn podane przez atrybut [$database];
  • wiersze 17–22: wykonuje się tyle razy, ile to konieczne, instrukcję wstawiania przygotowaną w wierszach 12–15;

Kod metody [fillTableConstantes] jest następujący:


private function fillTableConstantes($connexion): void {
    // skrót
    $database = $this->database;
    // opróżniamy tabelę na wypadek, gdyby coś w niej było
    $statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
    $statement->execute();
    // przygotowuje się wstawianie
    $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);
    // wykonuje się przygotowane polecenie
    $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()
    ]);
  }

Komentarze

  • wiersz 1: metoda [fillTableConstantes] otrzymuje jako parametr otwarte połączenie. Wiadomo ponadto, że w ramach tego połączenia rozpoczęła się transakcja;
  • wiersze 5–6: tabela [tbconstantes] jest opróżniana;
  • wiersze 9–31: przygotowanie polecenia wstawiania SQL. Jest ono skomplikowane, ponieważ w tej operacji wstawiania należy zainicjować 10 kolumn, a nazwy kolumn trzeba pobrać z atrybutu [$database];
  • wiersze 33–44: wykonanie polecenia wstawiania. Do wstawienia jest tylko jeden wiersz. Również w tym przypadku kod jest skomplikowany, ponieważ konieczne jest pobranie wartości do wstawienia z atrybutu [$taxAdminData];

13.3.4. Skrypt główny

Image

Image

Skrypt główny wykorzystuje warstwę [dao] do przeprowadzania transferu danych:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

// obsługa błędów przez PHP
// ini_set("display_errors", "0");
// dołączenie interfejsu i klas
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";
//
// definicja stałych
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // tworzenie warstwy [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // przesyłanie danych do bazy
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // wyświetlanie błędu
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// koniec
print "Terminé\n";
exit;

Komentarze

  • wiersze 12–21: ładowanie klas i interfejsów aplikacji;
  • wiersze 24–24: dwa pliki jSON;
  • wiersz 30: instancjonowanie warstwy [dao] poprzez przekazanie do konstruktora dwóch plików jSON;
  • wiersz 32: przeprowadzamy transfer danych;

Po uruchomieniu tego kodu otrzymujemy następujący wynik w bazie danych:

Image

W kolumnie [3] widoczne są wartości przypisane przez MySQL do klucza głównego [id]. Numeracja zaczyna się od 1. Powyższy zrzut ekranu uzyskano po kilkukrotnym uruchomieniu skryptu.

Image

Image

13.4. Obliczanie podatku

Image

13.4.1. Architektura

Wersja 04 aplikacji do obliczania podatku wykorzystywała architekturę warstwową:

Image

Warstwa [dao] implementuje interfejs [InterfaceDao]. Stworzyliśmy klasę implementującą ten interfejs:

  • [DaoImpotsWithTaxAdminDataInJsonFile], która pobierała dane podatkowe z pliku jSON. Była to wersja 04;

Zaimplementujemy interfejs [InterfaceDao] za pomocą nowej klasy [DaoImpotsWithTaxAdminDataInDatabase], która będzie pobierać dane z urzędu skarbowego z bazy danych MySQL. Warstwa [dao], podobnie jak poprzednio, zapisze wyniki i błędy w plikach tekstowych oraz pobierze dane podatników również z pliku tekstowego. Tym razem jednak pliki tekstowe będą miały format jSON. Ponadto wiemy, że jeśli nadal będziemy przestrzegać interfejsu [InterfaceDao], warstwa [métier] nie będzie wymagała modyfikacji.

Image

13.4.2. Entyteta [TaxPayerData]

Image

Klasa [TaxPayerData] służy do enkapsulacji w klasie danych z następującego pliku 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
    }
]

Klasa [TaxPayerData] ma następujący wygląd:


<?php

// przestrzeń nazw
namespace Application;

// klasa danych
class TaxPayerData extends BaseEntity {
  // dane niezbędne do obliczenia podatku podatnika
  protected $marié;
  protected $enfants;
  protected $salaire;
  // wyniki obliczenia podatku
  protected $impôt;
  protected $surcôte;
  protected $décôte;
  protected $réduction;
  protected $taux;

  // metody pobierające i ustawiające

}

Uwagi

  • wiersz 7: klasa [TaxPayerData] dziedziczy po klasie [BaseEntity]. Ponieważ metody klasy nadrzędnej są wystarczające, klasa [TaxPayerData] nie definiuje żadnych własnych metod. Przypominamy, że atrybuty klasy [TaxPayerData] są identyczne z atrybutami pliku jSON [taxpayersdata.json];

13.4.3. Warstwa [dao]

13.4.3.1. Cechą [TraitDao]

Cechą [TraitDao] jest implementacja części interfejsu [InterfaceDao]. Przypomnijmy ten interfejs:


<?php

// przestrzeń nazw
namespace Application;

interface InterfaceDao {

  // odczyt danych podatników
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // odczyt danych z urzędu skarbowego (progi podatkowe)
  public function getTaxAdminData(): TaxAdminData;

  // zapisywanie wyników
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Cecha [TraitDao] implementuje metody [getTaxPayersData, saveResults] z interfejsu [InterfaceDao]. Ponieważ między wersjami 04 a 05 zmieniono definicję encji [TaxPayerData], musimy zaktualizować kod [TraitDao]:


<?php

// przestrzeń nazw
namespace Application;

trait TraitDao {

  // odczyt danych podatników
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // pobieranie danych podatników do tabeli
    $baseEntity = new BaseEntity();
    $baseEntity->setFromJsonFile($taxPayersFilename);
    $arrayOfAttributes = $baseEntity->getArrayOfAttributes();
    // tabela danych podatników
    $taxPayersData = [];
    // tablica błędów
    $errors = [];
    // przechodzenie w pętli przez tablicę atrybutów elementów typu [TaxPayerData]
    $i = 0;
    foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
      // weryfikacja
      $error = $this->check($attributesOfTaxPayerData);
      if (!$error) {
        // podatnik z wartością dodatnią
        $taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
      } else {
        // błąd + – odnotowuje się numer nieprawidłowego wpisu
        $error = ["numéro" => $i] + $error;
        $errors[] = $error;
      }
      // następny
      $i++;
    }
    // błędy zapisuje się w pliku JSON
    $string = "";
    foreach ($errors as $error) {
      $string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
    }
    $this->saveString($errorsFilename, $string);
    // wynik funkcji
    return $taxPayersData;
  }

  private function check(array $attributesOfTaxPayerData): array {
    // sprawdzamy dane z [$taxPayerData]
    // lista błędnych atrybutów
    $attributes = [];
    // stan cywilny musi wynosić „tak” lub „nie”
    $marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
    $erreur = ($marié !== "oui" and $marié !== "non");
    if ($erreur) {
      // odnotowuje się błąd
      $attributes[] = ["marié" => $marié];
    }
    // liczba dzieci musi być dodatnią liczbą całkowitą lub zerem
    $enfants = trim($attributesOfTaxPayerData["enfants"]);
    if (!preg_match("/^\d+$/", $enfants)) {
      // odnotowuje się błąd
      $erreur = TRUE;
      $attributes[] = ["enfants" => $enfants];
    } else {
      $enfants = (int) $enfants;
    }

    // wynagrodzenie musi być liczbą całkowitą dodatnią lub zerem (bez centów euro)
    $salaire = trim($attributesOfTaxPayerData["salaire"]);
    if (!preg_match("/^\d+$/", $salaire)) {
      // zauważono błąd
      $erreur = TRUE;
      $attributes[] = ["salaire" => $salaire];
    } else {
      $salaire = (int) $salaire;
    }

    // błąd?
    if ($erreur) {
      // zwrot z błędem
      return ["erreurs" => $attributes];
    } else {
      // powrót bez błędu
      return [];
    }
  }

  // zapis wyników
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // zapis tabeli [$taxPayersData] w pliku tekstowym [$resultsFileName]
    // jeśli plik tekstowy [$resultsFileName] nie istnieje, zostanie utworzony
    // tworzenie ciągu znaków jSON zawierającego wyniki
    $string = "[" . implode(",
", $taxPayersData) . "]";
    // zapisanie tego ciągu znaków
    $this->saveString($resultsFilename, $string);
  }

  // zapis wyników z tablicy w pliku tekstowym
  private function saveString(string $fileName, string $data): void {
    // zapisanie ciągu znaków [$data] w pliku tekstowym [$fileName]
    // jeśli plik tekstowy [$fileName] nie istnieje, zostanie utworzony
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Komentarze

  • [TraitDao] implementuje metody [getTaxPayersData] (wiersz 9) i [saveResults] (wiersz 86) interfejsu [InterfaceDao];
  • wiersz 9: metoda [getTaxPayersData] przyjmuje jako parametry:
    • [$taxPayersFilename]: nazwę pliku jSON zawierającego dane podatników [taxpayersdata.json];
    • [$errorsFilename]: nazwa pliku jSON zawierającego błędy [errors.json];
  • wiersze 11–13: zawartość pliku jSON zawierającego dane podatników jest przenoszona do tablicy asocjacyjnej [$arrayOfAttributes]. Jeśli plik jSON okaże się nieprzydatny, zostanie zgłoszony wyjątek [ExceptionImpots];
  • wiersz 15: tablica [$taxPayersData] będzie zawierać dane podatników zamknięte w obiektach typu [TaxPayerData];
  • wiersz 17: błędy zostaną zgromadzone w tablicy [$errors];
  • wiersze 99–33: tworzenie tabeli [$taxPayersData];
  • wiersz 22: przed umieszczeniem w typie [TaxPayerData] dane są sprawdzane. Metoda [check] zwraca:
    • tablicę [‘erreurs’=>[…]] zawierającą błędne atrybuty, jeśli dane są nieprawidłowe;
    • pustą tablicę, jeśli dane są poprawne;
  • wiersz 25: przypadek, w którym dane są prawidłowe. Tworzony jest nowy obiekt typu [TaxPayerData] i dodawany do tablicy [$taxPayersData];
  • wiersze 26–30: przypadek, w którym dane są nieprawidłowe. W komunikacie o błędzie odnotowuje się numer błędnego obiektu [TaxPayerData] w pliku jSON, aby użytkownik mógł go odnaleźć, a następnie błąd jest dodawany do tablicy [$errors];
  • wiersze 35–39: rejestruje się napotkane błędy w pliku jSON, przekazanym jako parametr w wierszu 9 pliku [$errorsFilename];
  • wiersz 41: zwracana jest tablica utworzonych obiektów [TaxPayerData]: taki był cel tej metody;
  • wiersze 44–83: prywatna metoda [check] sprawdza poprawność parametrów [marié, enfants, salaire] z tablicy [$attributesOfTaxPayerData] przekazanej jako parametr w wierszu 44. Jeśli występują błędne atrybuty, metoda gromadzi je w tablicy [$attributes] (wiersze 47, 53, 60, 70) w postaci tablicy [‘attribut erroné’=> valeur de l’attribut erroné];
  • wiersz 78: jeśli występują błędy, zwracana jest tablica [‘erreurs’=>$attributes];
  • wiersz 81: jeśli nie ma błędów, zwracana jest pusta tablica błędów;
  • wiersze 86–93: implementacja metody [saveResults] interfejsu [InterfaceDao];
  • wiersz 90: tworzy się ciąg znaków jSON, który ma zostać zapisany w pliku jSON [$resultsFilename] przekazanym jako parametr w wierszu 86. Należy utworzyć ciąg znaków jSON z tablicy:
    • każdy element tablicy jest oddzielony od następnego przecinkiem i znakiem nowej linii;
    • cała tablica jest ujęta w nawiasy kwadratowe [];
  • wiersz 92: ciąg znaków jSON jest zapisywany w pliku jSON [$resultsFilename];

13.4.3.2. Klasa [DaoImpotsWithTaxAdminDataInDatabase]

Klasa [DaoImpotsWithTaxAdminDataInDatabase] implementuje interfejs [InterfaceDao] w następujący sposób:


<?php

// przestrzeń nazw
namespace Application;

// definicja klasy ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
  // użycie cechy
  use TraitDao;
  // obiekt typu TaxAdminData zawierający dane dotyczące przedziałów podatkowych
  private $taxAdminData;
  // obiekt typu [Database] zawierający cechy charakterystyczne BD
  private $database;

  // twórca
  public function __construct(string $databaseFilename) {
    // zapisywana jest konfiguracja bazy danych jSON
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // przygotowuje się atrybut
    $this->taxAdminData = new TaxAdminData();
    try {
      // otwieramy połączenie z bazą danych
      $connexion = new \PDO(
        $this->database->getDsn(),
        $this->database->getId(),
        $this->database->getPwd());
      // chcemy, aby przy każdym błędzie SGBD zgłaszany był wyjątek
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // rozpoczynamy transakcję
      $connexion->beginTransaction();
      // wypełniamy tabelę przedziałów podatkowych
      $this->getTranches($connexion);
      // wypełnia się tabelę stałych
      $this->getConstantes($connexion);
      // transakcja zostaje pomyślnie zakończona
      $connexion->commit();
    } catch (\PDOException $ex) {
      // czy trwa jakaś transakcja?
      if (isset($connexion) && $connexion->inTransaction()) {
        // zakończenie transakcji z powodu niepowodzenia
        $connexion->rollBack();
      }
      // przekazujemy wyjątek do kodu wywołującego
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // zamyka się połączenie
      $connexion = NULL;
    }
  }

  // odczyt danych z bazy
  private function getTranches($connexion): void {

  }

  // odczyt tabeli stałych
  private function getConstantes($connexion): void {

  }

  // zwraca dane umożliwiające obliczenie podatku
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }

}

Komentarze

  • wiersz 4: zachowuje się przestrzeń nazw już wykorzystaną dla innych implementacji warstwy [dao];
  • wiersz 7: klasa [DaoImpotsWithTaxAdminDataInDatabase] implementuje interfejs [InterfaceDao];
  • wiersz 9: importujemy cechę [TraitDao]. Wiemy, że ta cecha implementuje część interfejsu. Jedyną metodą, którą pozostaje zaimplementować, jest metoda [getTaxAdminData] z wierszy 62–64. Metoda ta ogranicza się do zwracania prywatnego atrybutu [taxAdminData] z wiersza 11. Wynika z tego, że konstruktor będzie musiał zainicjować ten atrybut. Jest to jego jedyna rola;
  • wiersz 16: konstruktor otrzymuje jako jedyny parametr [$databaseFilename], który jest nazwą pliku jSON [database.json] definiującego bazę danych MySQL [dbimpots-2019] ;
  • wiersz 18: plik jSON [$databaseFilename] służy do utworzenia obiektu typu [Database], który jest tworzony i zapisywany w atrybucie [$database] z wiersza 13. Jeśli plik jSON nie mógł zostać poprawnie przetworzony, zgłoszono wyjątek [ExceptionImpots];
  • wiersz 20: tworzony jest obiekt [$this→taxAdminData], który konstruktor musi zainicjować;
  • wiersze 22–26: nawiązujemy połączenie z bazą danych. Zwróć uwagę na zapis [\PDO], który oznacza klasę [PDO] z PHP. W rzeczywistości, ponieważ znajdujemy się w przestrzeni nazw [Application], gdybyśmy wpisali po prostu [PDO], ta nazwa względna zostałaby poprzedzona bieżącą przestrzenią nazw, co dałoby klasę [Application\PDO], która nie istnieje;
  • wiersz 28: w przypadku błędu SGBD uruchomi \PDOException (wiersz 37);
  • wiersz 30: rozpoczynamy transakcję. Nie jest to tak naprawdę konieczne, ponieważ wykonane zostaną tylko dwa polecenia SQL, które nie modyfikują bazy danych. Robimy to jednak, aby odizolować się od innych użytkowników bazy;
  • wiersz 32: odczyt tabeli przedziałów podatkowych [tbtranches] odbywa się za pomocą prywatnej metody [getTranches] z wiersza 52;
  • wiersz 34: odczyt tabeli stałych obliczeniowych [tbconstantes] odbywa się za pomocą prywatnej metody [getConstantes] z wiersza 57;
  • wiersz 36: jeśli dotarliśmy do tego wiersza, oznacza to, że wszystko przebiegło pomyślnie. Zatwierdzamy zatem transakcję;
  • wiersze 37–42: jeśli dotarliśmy do tego miejsca, oznacza to, że wystąpił wyjątek. W związku z tym unieważniamy transakcję, jeśli jakakolwiek była w toku (wiersze 39–42). W wierszu 44, aby zapewnić spójność wyjątków, ponownie generujemy komunikat o otrzymanym wyjątku, tym razem w postaci wyjątku typu [ExceptionImpots];
  • wiersze 45–48: w każdym przypadku (niezależnie od tego, czy wystąpił wyjątek, czy nie) zamykamy połączenie;

Metoda [getTranches] wygląda następująco:


private function getTranches($connexion): void {
     // skróty
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
     // przygotowuje się zapytanie SELECT
    $statement = $connexion->prepare(
      "select {$database->getColLimites()}," .
      " {$database->getColCoeffR()}," .
      " {$database->getColCoeffN()}" .
      " from {$database->getTableTranches()}");
     // wykonanie przygotowanego zlecenia z wartościami przedziałów podatkowych
    $statement->execute();
     // analiza wyniku
    $limites = [];
    $coeffR = [];
    $coeffN = [];
     // wypełnianie trzech tabel
    while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
      $limites[] = (float) $tranche->{$database->getColLimites()};
      $coeffR[] = (float) $tranche->{$database->getColCoeffR()};
      $coeffN[] = (float) $tranche->{$database->getColCoeffN()};
    }
    // zapisywanie danych w atrybucie [$taxAdminData] klasy
    $taxAdminData->setFromArrayOfAttributes([
      "limites" => $limites,
      "coeffR" => $coeffR,
      "coeffN" => $coeffN
    ]);
  }

Komentarze

  • wiersz 1: metoda otrzymuje jako parametr [$connexion], który jest otwartym połączeniem, w którym trwa transakcja;
  • wiersze 2–4: tworzy się dwa skróty, aby uniknąć konieczności wpisywania [$this->database] i [$taxAdminData = $this->taxAdminData] w całym kodzie. Są to kopie odwołań do obiektów, a nie kopie samych obiektów;
  • wiersze 6–10: przygotowuje się polecenie SELECT, a następnie wykonuje je w wierszu 12;
  • wiersze 13–22: wykorzystywany jest wynik SELECT. Otrzymane informacje są gromadzone w trzech tablicach [limites, coeffR, coeffN];
  • wiersze 24–28: trzy tablice są wykorzystywane do zainicjowania atrybutu [$this->taxAdminData] klasy;

Prywatna metoda [getConstantes] wygląda następująco:


private function getConstantes($connexion): void {
    // skróty
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // przygotowuje się zapytanie 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);
    // wykonuje się przygotowane polecenie
    $statement->execute();
    // wykorzystujemy wynik – tutaj jest tylko jeden wiersz
    $row = $statement->fetch(\PDO::FETCH_OBJ);
    // inicjujemy atrybut [$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()});
  }

Komentarze

  • wiersz 1: metoda otrzymuje jako parametr [$connexion], który jest otwartym połączeniem, w którym trwa transakcja;
  • wiersze 2–4: tworzone są dwa skróty, aby uniknąć konieczności wpisywania [$this->database] i [$taxAdminData = $this->taxAdminData] w całym kodzie. Są to kopie odwołań do obiektów, a nie kopie samych obiektów;
  • wiersze 6–15: przygotowywane jest polecenie SELECT, a następnie wykonywane w wierszu 15;
  • wiersze 17–29: wykorzystywany jest wynik SELECT. Pobrane informacje służą do zainicjowania atrybutu [$this->taxAdminData] klasy;

Uwaga: należy zauważyć, że klasa nie jest zależna od SGBD ani MySQL. To kod wywołujący określa SGBD używany za pośrednictwem DSN z bazy danych.

13.4.4. Warstwa [métier]

Image

  • właśnie zaimplementowaliśmy warstwę [dao] (3);
  • ponieważ zachowaliśmy zgodność z interfejsem [InterfaceDao], warstwa [métier] (2) może teoretycznie pozostać niezmieniona. Jednak nie zmieniliśmy jedynie warstwy [dao]. Zmodyfikowaliśmy również elementy, które są wspólne dla wszystkich warstw;

Warstwa [métier] implementuje następujący interfejs [InterfaceMetier]:


<?php

// przestrzeń nazw
namespace Application;

interface InterfaceMetier {

  // obliczanie podatków podatnika
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // obliczanie podatków w trybie wsadowym
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • wiersz 12: metoda [executeBatchImpots] korzysta teraz z pliku jSON [$taxPayersFileName], podczas gdy w wersji 04 był to zwykły plik tekstowy.;

W wersji 04 metoda [executeBatchImpots] wyglądała następująco:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // pozwala na zgłaszanie wyjątków pochodzących z warstwy [dao]
    // pobieranie danych podatników
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabela wyników
    $results = [];
    // przetwarzamy je
    foreach ($taxPayersData as $taxPayerData) {
      // obliczamy podatek
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // uzupełnianie [$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"]);
      // wpisuje się wynik do tabeli wyników
      $results [] = $taxPayerData;
    }
    // zapisywanie wyników
    $this->dao->saveResults($resultsFileName, $results);
  }
  • wiersz 15 jest teraz błędny. W nowej definicji klasy [TaxPayerData] metoda [setMontant] już nie istnieje;

W wersji 05 metoda [executeBatchImpots] będzie miała następujący wygląd:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // pozwala się na zgłaszanie wyjątków pochodzących z warstwy [dao]
    // pobieramy dane podatników
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabela wyników
    $results = [];
    // przetwarzanie wyników
    foreach ($taxPayersData as $taxPayerData) {
      // obliczamy podatek
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // uzupełnianie [$taxPayerData]
      $taxPayerData->setFromArrayOfAttributes($result);
      // wpisuje się wynik do tabeli wyników
      $results [] = $taxPayerData;
    }
    // zapisywanie wyników
    $this->dao->saveResults($resultsFileName, $results);
  }

Uwagi

  • wiersz 15: zamiast korzystać z poszczególnych metod setterów klasy [TaxPayerData], stosuje się jej globalną metodę setterową [setFromArrayOfAttributes];
  • pozostała część kodu nie wymaga zmian;

13.4.5. Główny skrypt

Image

  • właśnie zaimplementowaliśmy warstwy [dao] (3) i [métier] (2);
  • pozostaje nam jeszcze napisać skrypt główny (1);

Skrypt główny jest analogiczny do tego z wersji 04:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

// obsługa błędów za pomocą PHP
//ini_set("display_errors", "0");
// dołączanie interfejsów i klas
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";
//
// definicja stałych
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 {
  // utworzenie warstwy [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // utworzenie warstwy [métier]
  $métier = new Metier($dao);
  // obliczanie podatku w trybie wsadowym
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // wyświetlenie błędu
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// koniec
print "Terminé\n";
exit;


Komentarze

  • wiersze 12–22: ładowanie wszystkich plików wersji 05;
  • wiersze 25–29: nazwy poszczególnych plików jSON aplikacji;
  • wiersz 33: tworzenie warstwy [dao];
  • wiersz 35: utworzenie warstwy [métier];
  • wiersz 37: wywołanie metody [executeBatchImpots] warstwy [métier];

Wyniki

Aplikacja generuje dwa pliki jSON:

  • [resultats.json]: wyniki różnych obliczeń podatków;
  • [errors.json]: zawierający informacje o błędach wykrytych w pliku jSON i [taxpayersdata.json];

Plik [errors.json] ma następującą treść:


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

Oznacza to, że w pliku [taxpayersdata.json] element nr 1 tabeli podatników jest błędny. Plik [taxpayersdata.json] miał następującą treść:


[
    {
        "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
    }
]

Plik wyników [resultats.json] ma natomiast następującą postać:


[
    {
        "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
    }
]

Wyniki te są zgodne z wynikami z wersji 04.

13.5. Testy [Codeception]

Podobnie jak w przypadku wersji 04, w sekcji „link”, napiszemy testy [Codeception] dla wersji 05.

Image

13.5.1. Test warstwy [dao]

Test [DaoTest.php] wygląda następująco:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

// katalogi główne
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// dołączanie interfejsów i klas
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";
// biblioteki zewnętrzne
require_once VENDOR . "/autoload.php";

// definicja stałych
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();
    // tworzenie warstwy [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // testy
  public function testTaxAdminData() {
    // stałe obliczeniowe
    $this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());

  }

}

Komentarze

  • wiersze 9–33: definicja środowiska testowego. Wykorzystujemy to samo środowisko, co w głównym skrypcie [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] opisanym w akapicie „link”;
  • wiersze 39–44: tworzenie warstwy [dao];
  • wiersz 43: atrybut [$this→taxAdminData] zawiera dane do przetestowania;
  • wiersze 47–51: metoda [testTaxAdminData] jest tą samą metodą, która została opisana w akapicie „link”;

Wyniki testu są następujące:

Image

13.5.2. Test warstwy [métier]

Test [MetierTest.php] wygląda następująco:


<?php

// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare (strict_types=1);

// przestrzeń nazw
namespace Application;

// katalogi główne
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// dołączanie interfejsów i klas
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";
// biblioteki zewnętrzne
require_once VENDOR . "/autoload.php";

// definicja stałych
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 {
  // warstwa biznesowa
  private $métier;

  public function __construct() {
    parent::__construct();
    // tworzenie warstwy [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // tworzenie warstwy [métier]
    $this->métier = new Metier($dao);
  }

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

}

Komentarze

  • wiersze 9–33: definicja środowiska testowego. Wykorzystujemy to samo środowisko, co w głównym skrypcie [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] opisanym w akapicie „link”;
  • wiersze 39–45: tworzenie warstw [dao] i [métier];
  • wiersz 44: atrybut [$this→métier] odwołuje się do warstwy [métier];
  • wiersze 47–64: metody [test1, test2…, test11] są zgodne z opisem w akapicie „link”;

Wyniki testu są następujące:

Image