8. Ćwiczenie praktyczne – wersja 3
Wracamy do ćwiczenia omówionego wcześniej (punkty 4.3 i 4.4), aby rozwiązać je za pomocą kodu PHP wykorzystującego klasę.
8.1. Struktura drzewa skryptów

8.2. Wyjątek [ExceptionImpots]
W wersji 03, gdy konstruktor lub metoda klasy napotka błąd, wygeneruje wyjątek typu [ExceptionImpots] o następującej treści:
Komentarze
- wiersz 4: klasa [ExceptionImpots] znajduje się w przestrzeni nazw [Application];
- wiersz 6: klasa [ExceptionImpots] dziedziczy po klasie predefiniowanej w PHP [RuntimeException];
- wiersz 8: konstruktor oczekuje dwóch parametrów:
- $message: to komunikat o błędzie powiązany z wyjątkiem;
- $code: to kod błędu powiązany z wyjątkiem. Jeśli nie występuje, zostanie użyty kod 0;
8.3. Klasa [TaxAdminData]
W wersji 02 dane administracji podatkowej zostały zebrane:
- najpierw w pliku jSON;
- następnie z tego pliku jSON do tablicy asocjacyjnej;
W wersji 03 dane administracji podatkowej nadal znajdują się w pliku [taxadmindata.json], ale mają inne nazwy atrybutów:
{
"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
}
W wersji 02 plik ten służył do zainicjowania tablicy asocjacyjnej. W wersji 03 plik ten zainicjuje następującą klasę [TaxAdminData]:
<?php
namespace Application;
class TaxAdminData {
// przedziały podatkowe
private $limites;
private $coeffR;
private $coeffN;
// stałe służące do obliczania podatku
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// inicjalizacja
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// pobieranie zawartości pliku danych podatkowych
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// błąd?
if (!$fileContents) {
// odnotowuje się błąd
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// pobieranie kodu jSON z pliku konfiguracyjnego do tablicy asocjacyjnej
$arrayTaxAdminData = \json_decode($fileContents, true);
// błąd?
if ($arrayTaxAdminData === FALSE) {
// odnotowuje się błąd
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// błąd?
if ($erreur) {
// rzucamy wyjątek
throw new ExceptionImpots($message);
}
// inicjalizacja atrybutów klasy
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// sprawdzamy, czy wszystkie klucze zostały zainicjowane
$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é");
}
}
// sprawdzanie, czy wszystkie wartości są liczbami rzeczywistymi
foreach ($this as $key => $value) {
// $value musi być liczbą rzeczywistą >=0 lub tablicą liczb rzeczywistych >=0
$result = $this->check($value);
// błąd?
if ($result->erreur) {
// rzucamy 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;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// ciąg JSON obiektu
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// metody pobierające i ustawiające
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;
}
…
}
Komentarze
- wiersze 6–20: atrybuty, które będą przechowywać atrybuty o tej samej nazwie z plików jSON i [taxadmindata.json]. Jest to ważna kwestia: atrybuty klasy [TaxAdminData] są identyczne z atrybutami plików jSON i [taxadmindata.json]. Ta cecha znacznie ułatwia pisanie kodu;
- klasa [TaxAdminData] nie posiada konstruktora. W PHP nie ma możliwości posiadania wielu konstruktorów. Zdefiniowanie jednego z nich uniemożliwia zatem zainicjowanie obiektu w inny sposób. W dalszej części nasze klasy nie będą miały konstruktora, ale kilka metod typu [setFromQqChose], które pozwolą na ich inicjalizację na różne sposoby. Tworzenie obiektu typu [TaxAdminData] odbywa się zatem za pomocą wyrażenia:
- wiersz 23: metoda [setFromJsonFile] inicjuje atrybuty klasy, przypisując im wartości o tej samej nazwie z pliku [$jsonFilename];
- wiersze 24–42: plik jSON jest wykorzystywany do utworzenia tablicy asocjacyjnej [$arrayTaxAdminData]. Kod ten pojawił się już wcześniej w skrypcie [main.php] z wersji 02;
- wiersze 44–47: jeśli podczas przetwarzania pliku jSON wystąpił błąd, generowany jest wyjątek. Zostanie on przekazany do głównego skryptu [main.php];
- wiersze 48–51: inicjowane są atrybuty klasy. Wykorzystujemy tutaj fakt, że tablica asocjacyjna [$arrayTaxAdminData] oraz klasa [TaxAdminData] mają atrybuty o takich samych nazwach jak wartości pochodzące z pliku jSON;
- wiersze 53–57: sprawdzamy, czy wszystkie atrybuty klasy [TaxAdminData] zostały zainicjowane;
- wiersz 53: wyrażenie [get_object_vars($this)] zwraca tablicę asocjacyjną, której atrybuty są atrybutami obiektu [$this], a zatem atrybutami klasy [TaxAdminData]. Należy tu pamiętać, że operacja inicjalizacji w wierszach 48–51 mogła dodać atrybuty do obiektu [$this]. Jeśli więc napiszemy:
to atrybut [x] zostanie dodany do obiektu [$this], nawet jeśli atrybut ten nie został zadeklarowany w klasie [TaxAdminData]. Pewne jest, że atrybuty z wierszy 6–20 rzeczywiście należą do obiektu [$this], ale mogły nie zostać zainicjowane. Łatwo popełnić taki błąd – wystarczy pomylić nazwę atrybutu w pliku [taxadmindata.json];
- wiersze 54–57: przeglądamy wszystkie atrybuty obiektu [$this] i jeśli któryś z nich nie został zainicjowany, generujemy wyjątek;
- atrybut może zostać zainicjowany nieprawidłową wartością. W pliku PHP nie ma możliwości przypisania typu do atrybutów. Dlatego operacja:
jest możliwa, podczas gdy atrybut [$plafondQfDemiPart] powinien być typu rzeczywistego;
- wiersze 59–71: sprawdza się, czy każdy z atrybutów klasy ma wartość liczbową typu realnego, dodatnią lub zerową. Zadanie to realizuje funkcja [check] w wierszu 76. Jej parametr [$value] jest albo pojedynczą wartością, albo tablicą wartości;
- wiersz 62: funkcja [check] zwraca obiekt typu [\stdClass] z dwoma atrybutami:
- [erreur]: przyjmuje wartość TRUE, jeśli wystąpił błąd, w przeciwnym razie przyjmuje wartość FALSE;
- [value]: rzeczywista wartość liczbowa odpowiadająca parametrowi [$value] przekazanemu jako parametr, wiersz 62;
- wiersz 64: sprawdzamy, czy weryfikacja zakończyła się powodzeniem, czy nie;
- wiersz 66: jeśli atrybut nie jest liczbą rzeczywistą dodatnią lub zerem, zgłaszamy wyjątek;
- wiersz 69: w przeciwnym razie zapisujemy jego wartość liczbową;
- wiersz 73: zwracamy obiekt [$this] jako wynik;
Funkcja [check] wygląda następująco:
private function check($value): \stdClass {
// $value jest albo tablicą elementów, albo pojedynczym elementem
// tworzy się tablicę
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// przekształca się tablicę elementów o nieznanym typie 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;
}
Komentarze
- wiersz 1: parametr [$value] jest albo tablicą, albo pojedynczym elementem. Ponadto nie znamy jego typu. Wartość pochodzi z pliku [taxadmindata.json]. W zależności od wartości zapisanych w tym pliku odczytane wartości mogą być liczbami całkowitymi, liczbami rzeczywistymi, ciągami znaków lub wartościami logicznymi. Na przykład:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
W przypadku 1 wartość jest typu [entier], w przypadku 2 typu [réel], w przypadku 3 ma typ [string] i może zostać przekonwertowana na liczbę, w przypadku 4 ma typ [string] i nie może zostać przekonwertowana na liczbę;
- wiersze 4–8: tworzymy tablicę na podstawie parametru [$value] otrzymanego w wierszu 1;
- wiersz 10: tablica, którą wypełnimy liczbami rzeczywistymi;
- wiersz 11: wynikiem będzie obiekt typu [\stdClass];
- wiersz 13: wyrażenie relacyjne określające liczbę rzeczywistą dodatnią lub zerową;
- wiersze 14–24: sprawdzamy, czy wszystkie elementy tablicy [$tableau] są liczbami rzeczywistymi dodatnimi lub zerowymi, a następnie wypełniamy tablicę [$newTableau] tymi elementami przekształconymi na typ [float] (wiersz 17);
- wiersze 18–23: gdy tylko wykryty zostanie element, który nie jest liczbą rzeczywistą dodatnią lub zerem, odnotowuje się błąd w wyniku i zwraca się ten wynik;
- wiersze 25–34: przypadek, w którym wszystkie elementy tablicy [$tableau] zostały uznane za poprawne;
- wiersz 32: zwracana wartość [$result→value] jest tablicą liczb rzeczywistych [float] lub pojedynczą liczbą rzeczywistą;
Funkcja [__toString] w wierszach 82–85 zwraca ciąg znaków jSON zawierający atrybuty i wartości obiektu [$this].
Wiersze 87–110: metody getter i setter klasy;
Uwaga: czasami pisanie wszystkich metod get/set dla klasy może być nieco uciążliwe, zwłaszcza gdy jest wiele atrybutów. NetBeans może automatycznie wygenerować te metody, a także konstruktor. W tym celu wystarczy podać atrybuty [1]:

- w [2], kliknij prawym przyciskiem myszy w miejscu, w którym chcesz wstawić kod, a następnie wybierz opcję [Insert Code];

- na [4], zaznacz, że chcesz wygenerować konstruktor;
- w [5] zaznacz wszystkie atrybuty: oznacza to, że chcesz, aby konstruktor miał parametr dla każdego z atrybutów;
- w [6] należy zastosować styl konstruktorów języka Java;
- w [7] należy zaznaczyć, że wyraźnie chcesz, aby przed konstruktorem znajdowało się słowo kluczowe [public];
- w [8] zatwierdź;

- w [9] program NetBeans wygenerował konstruktor. Nie był jednak w stanie określić typów parametrów, ponieważ ich nie zna. Dodaj je samodzielnie: [10];
Aby wygenerować metody getter i setter, powtórz kroki 2–4, a w kroku 4 wybierz [Getter and Setter]:

- w [5] należy zaznaczyć, że potrzebne są metody getter i setter dla każdego z atrybutów;
- w [6] należy wskazać, że chcesz, aby metody getter i setter były zgodne ze stylem stosowanym w Javie: setAttribut, getAttribut;
- w pliku [7] należy wskazać, że te metody getter i setter mają być publiczne;
- w pliku [8] zatwierdź zmiany;

- w pliku [9], metody getter i setter wygenerowane przez NetBeans;
Usuń te metody getter i setter i powtórz kroki 2–7.
- w pliku [8] zaznacz opcję [Fluent Setter], której wcześniej nie zaznaczyliśmy;
Otrzymany wynik jest następujący:

Każda metoda setter kończy się operacją [return $this]. Pozwala to zainicjować atrybuty w następujący sposób:
W rzeczywistości wartość [$data→setLimites($limites)] (wiersz 32 kodu) wynosi [$this], a zatem w tym przypadku [$data]. Można zatem wywołać metodę [setCoeffR($coeffR)] tego obiektu i tak dalej, ponieważ ta metoda z kolei zwraca [$this] (wiersz 37 kodu). Taki sposób definiowania metod klasy, w wyniku którego metody, które nie powinny zwracać żadnej wartości, zwracają obiekt [$this], nazywa się pisaniem płynnym. Ułatwia to korzystanie z tych metod.
8.4. Interfejs [InterfaceImpots]
Teraz definiujemy następujący interfejs [InterfaceImpots]: [InterfaceImpots.php]:
<?php
// przestrzeń nazw
namespace Application;
interface InterfaceImpots {
// pobieranie danych dotyczących przedziałów podatkowych umożliwiających obliczenie podatku
// może wywołać wyjątek ExceptionImpots
public function getTaxAdminData(): TaxAdminData;
// interfejs potrafi obliczyć podatek
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// interfejs potrafi przetwarzać dane z plików tekstowych
// $usersFilename: plik danych użytkownika zawierający stan cywilny, liczbę dzieci oraz roczne wynagrodzenie
// $resultsFilename: plik wyników zawierający stan cywilny, liczbę dzieci, roczne wynagrodzenie oraz kwotę podatku
// $errorsFilename: plik zawierający wykaz napotkanych błędów
// może wywołać wyjątek ExceptionImpots
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Komentarze
- wiersz 4: interfejs znajduje się w przestrzeni nazw [Application];
- wiersz 6: interfejs umożliwiający obliczanie podatków;
- wiersz 10: metoda [getTaxAdminData] umożliwi pobranie danych z urzędu skarbowego do obiektu typu [TaxAdminData], który właśnie przedstawiliśmy. Ponieważ dane te mogą znajdować się w pliku, bazie danych, a nawet w sieci, metoda [getTaxAdminData] może nie uzyskać danych. W takim przypadku wygeneruje wyjątek typu [ExceptionImpots]. Jest to standardowa metoda w programowaniu obiektowym służąca do zgłaszania błędu napotkanego w metodzie lub konstruktorze;
- wiersz 13: metoda [calculerImpot] pozwoli obliczyć podatek dla jednego użytkownika;
- wiersz 20: metoda [executeBatchImpots] służy do obliczania podatku dla wielu podatników:
- [$usersFileName] to nazwa pliku tekstowego zawierającego dane podatników;
- [$resultsFileName] to nazwa pliku tekstowego zawierającego kwotę podatku dla tych podatników;
- [$errorsFileName] to nazwa pliku tekstowego zawierającego błędy napotkane podczas przetwarzania tych plików;
Zawartość pliku tekstowego [$usersFileName] może wyglądać następująco:
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
Należy zauważyć, że wiersze 5 i 7 zawierają błędne dane.
Zawartość pliku tekstowego [$resultsFileName] będzie zatem następująca:
a zawartość pliku tekstowego [$errorsFileName] będzie następująca:
8.5. Klasa [Utilitaires]
Ponadto definiujemy klasę [Utilitaires] w pliku [Utilitaires.php]:
<?php
// przestrzeń nazw
namespace Application;
// klasa funkcji pomocniczych
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// usuwamy znacznik końca linii z $ligne, jeśli istnieje
$longueur = strlen($ligne); // długość wiersza
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// koniec – zwracamy wiersz
return($ligne);
}
}
Komentarze
- wiersz 4: klasa [Utilitaires] jest również umieszczona w przestrzeni nazw [Exemples];
- wiersz 9: metoda [cutNewLinechar] usuwa ewentualny znak końca linii z tekstu przekazanego jej jako parametr. Zwraca w ten sposób utworzony nowy wiersz. Należy zauważyć, że jest to metoda statyczna, co oznacza, że będzie wywoływana w postaci [Utilitaires::cutNewLineChar];
8.6. Klasa abstrakcyjna [AbstractBaseImpots]
Interfejs [InterfaceImpots] zostanie zaimplementowany przez następującą klasę abstrakcyjną [AbstractBaseImpots]: [AbstractBaseImpots.php]:
<?php
// przestrzeń nazw
namespace Application;
// definicja klasy abstrakcyjnej AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// dane urzędu skarbowego
private $taxAdminData = NULL;
// dane niezbędne do obliczenia podatku
abstract function getTaxAdminData(): TaxAdminData;
// obliczenie podatku
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié: tak, nie
// $enfants: liczba dzieci
// $salaire: roczne wynagrodzenie
// $this->taxAdminData: dane z urzędu skarbowego
//
// sprawdzamy, czy posiadamy dane z urzędu skarbowego
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// obliczenie podatku z uwzględnieniem dzieci
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// obliczenie podatku bez uwzględnienia dzieci
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// zastosowanie limitu ilorazu rodzinnego
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euro na pierwsze dwoje dzieci
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euro na pierwsze dwoje dzieci, dwukrotnie więcej na kolejne
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// bierzemy wyższy podatek
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"];
}
// obliczenie ewentualnej ulgi
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// obliczenie ewentualnej ulgi podatkowej
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// wynik
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 {
…
// wynik
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=wynagrodzenieRoczne-ulga
// ulga ma dolną i górną granicę
private function getRevenuImposable(float $salaire): float {
…
// wynik
return floor($revenuImposable);
}
// oblicza ewentualną obniżkę
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// wynik
return ceil($décôte);
}
// oblicza ewentualną redukcję
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// wynik
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
Komentarze
- wiersz 4: klasa [AbstractBaseImpots] będzie znajdować się w przestrzeni nazw [Application], podobnie jak pozostałe elementy tworzonej aplikacji;
- wiersz 7: klasa [AbstractBaseImpots] implementuje interfejs [InterfaceImpots];
- wiersz 9: dane urzędu skarbowego zostaną umieszczone w atrybucie [$taxAdminData];
- wiersz 12: implementacja metody [getTaxAdminData] z interfejsu. Nie wiemy jeszcze, jak zdefiniować tę metodę: w poprzednim akapicie widzieliśmy przykład, w którym dane z urzędu skarbowego zostały pobrane z pliku jSON. Zobaczymy jeszcze jeden przypadek, w którym dane będą pobierane z bazy danych. Zadaniem klas pochodnych będzie zdefiniowanie treści metody [getTaxAdminData]. Oba poprzednie przypadki doprowadzą do powstania dwóch klas pochodnych. Metoda [getTaxAdminData] jest zatem zadeklarowana jako abstrakcyjna, co automatycznie sprawia, że sama klasa staje się abstrakcyjna (wiersz 7);
- wiersze 15–64: funkcja obliczania podatku, którą znamy już z akapitów link i link;
- wersja 02 umieszczała dane urzędu skarbowego w tablicy asocjacyjnej [$taxAdminData]. Wersja 03 umieszcza je w atrybucie [$this→taxAdminData]. Pierwszą różnicą między tymi dwoma rozwiązaniami jest różnica w widoczności danych podatkowych:
- w wersji 02 tablica asocjacyjna [$taxAdminData] nie miała widoczności globalnej. Była zatem przekazywana jako parametr do wszystkich funkcji obliczających podatek;
- w wersji 03 atrybut [$this→taxAdminData] ma widoczność globalną dla wszystkich metod klasy. Nie jest więc przekazywany jako parametr do wszystkich funkcji obliczających podatek;
- druga różnica wynika z faktu, że w wersji 03 funkcje zostały zastąpione metodami klasy. Każde wywołanie metody odbywa się teraz za pomocą wyrażenia [$this→getMéthode(…)] (wiersze 27, 31, 57, 60);
- trzecią różnicą jest to, że gdy metoda [calculerImpot] rozpoczyna działanie, nie wie, czy potrzebny jej atrybut [private $taxAdminData] został zainicjowany. Konstruktor klasy bowiem go nie inicjuje. Zadaniem metody [calculerImpot] jest zatem zainicjowanie go za pomocą metody [getTaxAdminData] z wiersza 12. Odbywa się to w wierszach 23–25;
- poza tymi różnicami metody obliczania podatku pozostają takie same jak w poprzednich wersjach;
Funkcja [executeBatchImpots] wygląda następująco:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// podczas pracy z plikami może wystąpić sporo błędów
try {
// błędy podczas otwierania pliku
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// otwieranie pliku wyników
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// odczyt danych użytkownika
// każdy wiersz ma następujący format: stan cywilny, liczba dzieci, roczne wynagrodzenie
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// przetwarzamy bieżący wiersz z pliku danych użytkownika
// która ma postać: stan cywilny, liczba dzieci, roczne wynagrodzenie
$num = 1; // numer bieżącego wiersza
$nbErreurs = 0; // liczba napotkanych błędów
while ($ligne = fgets($data, 100)) {
// debug
// print „wiersz nr ” . ($i + 1) . „: ” . $ligne;
// usuwamy ewentualny znak końca linii
$ligne = Utilitaires::cutNewLineChar($ligne);
// pobieramy 3 pola: żona:dzieci:wynagrodzenie, które tworzą $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// sprawdzamy je
// stan cywilny musi wynosić „tak” lub „nie”
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// liczba dzieci musi być liczbą całkowitą
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// wynagrodzenie jest liczbą całkowitą bez centów euro
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// błąd?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// obliczamy podatek
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// wynik zapisuje się w pliku wyników
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// następny wiersz
$num++;
}
// czy są błędy?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// ponownie generuje się wyjątek
throw $ex;
} finally {
// zamyka się wszystkie pliki
fclose($data);
fclose($results);
fclose($errors);
}
}
Komentarze do kodu
- wiersz 1: funkcja przyjmuje trzy parametry:
- [$usersFileName]: nazwa pliku tekstowego zawierającego dane podatników. Każdy wiersz tekstu zawiera dane jednego podatnika w następującej formie: stan cywilny (tak / nie), liczba dzieci, roczne wynagrodzenie:
- (ciąg dalszy)
- [$resultsFileName]: nazwa pliku tekstowego, który będzie zawierał wyniki. Każdy wiersz tekstu będzie miał następującą postać:
- (ciąg dalszy)
- [$errorsFileName]: nazwa pliku tekstowego zawierającego błędy:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- wiersz 3: ponieważ niektóre operacje mogą wywołać wyjątek, cały kod metody jest otoczony blokiem try / catch / finally;
- wiersze 3–19: otwierane są trzy pliki. Wyjątek jest zgłaszany, gdy tylko otwarcie pliku nie powiedzie się;
- wiersz 24: wiersze pliku [$data] są odczytywane pojedynczo, po maksymalnie 100 znaków (wszystkie wiersze mają mniej niż 100 znaków);
- wiersz 28: używana jest metoda statyczna [Utilitaires::cutNewLineChar] w celu usunięcia ewentualnego znaku końca wiersza;
- wiersz 30: pobierane są trzy elementy z odczytanego wiersza;
- wiersze 33–52: sprawdzana jest poprawność trzech elementów. W tym przypadku nie zgłaszamy wyjątku w razie wystąpienia błędu, lecz zapisujemy komunikat o nim w pliku tekstowym [$errors] (wiersz 55);
- wiersz 59: jeśli odczytany wiersz jest prawidłowy, przeprowadzane jest obliczenie podatku. Wynik otrzymujemy w postaci tablicy asocjacyjnej ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
- wiersz 61: do uzyskanego wyniku dodaje się klucze [marié, enfants, salaire];
- wiersz 61: wynik jest zapisywany w pliku tekstowym [$results] w postaci ciągu znaków jSON odpowiadającego uzyskanym wynikom;
- wiersze 68–70: po zakończeniu przetwarzania pliku [$data] sprawdzana jest liczba napotkanych błędnych wierszy. Jeśli jest co najmniej jeden, generowany jest wyjątek;
- wiersze 71–74: przechwytuje się wyjątek, który mógł zostać wygenerowany przez kod, i natychmiast go ponownie generuje (wiersz 73). Celem tego zabiegu jest umożliwienie umieszczenia klauzuli [finally] w wierszach 74–79: niezależnie od tego, w jaki sposób zakończy się wykonywanie kodu metody, wszystkie trzy pliki, które mogły zostać otwarte przez ten kod, zostaną zamknięte. Zamknięcie pliku, który nie został otwarty, nie powoduje błędu;
8.7. Klasa [ImpotsWithTaxAdminDataInJsonFile]
Klasa abstrakcyjna [AbstractBaseImpots] nie implementuje metody [getTaxAdminData] z interfejsu [InterfaceImpots]. Musimy zatem zdefiniować ją w klasie pochodnej. Robimy to w następującej klasie pochodnej [ImpotsWithTaxAdminDataInJsonFile]:
<?php
// przestrzeń nazw
namespace Application;
// definicja klasy ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// atrybut typu Data
private $taxAdminData;
// konstruktor
public function __construct(string $jsonFileName) {
// inicjuje się $this->taxAdminData na podstawie pliku jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// zwraca dane umożliwiające obliczenie podatku
public function getTaxAdminData(): TaxAdminData {
// zwraca atrybut [$this->taxAdminData]
return $this->taxAdminData;
}
}
Komentarze
- wiersz 7: klasa [ImpotsWithTaxAdminDataInJsonFile] rozszerza klasę abstrakcyjną [AbstractBaseImpots]. Będzie musiała zdefiniować metodę [getTaxAdminData], której jej klasa nadrzędna nie zdefiniowała;
- wiersz 9: atrybut [$taxAdminData] będzie zawierał dane urzędu skarbowego;
- wiersze 12–15: konstruktor otrzymuje jako jedyny parametr nazwę pliku jSON zawierającego dane podatkowe;
- wiersz 14: tworzony jest obiekt typu [TaxAdminData], a następnie jest on inicjowany. Operacja ta może wywołać wyjątek typu [ExceptionImpots]. Wyjątek ten zostanie przekazany do głównego skryptu [main.php];
- wiersze 18–20: nadajemy treść metodzie [getTaxAdminData], której klasa nadrzędna nie zdefiniowała. W tym przypadku wystarczy, aby atrybut [$this->taxAdminData] został zainicjowany przez konstruktor;
8.8. Skrypt [main.php]
Klasy i interfejs te są wykorzystywane przez następujący skrypt [main.php]:
<?php
// ścisłe przestrzeganie zadeklarowanych typów parametrów funkcji
declare(strict_types = 1);
// przestrzeń nazw
namespace Application;
// dołączanie interfejsów i klas
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";
// test -----------------------------------------------------
// definicja stałych
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// tworzymy obiekt ImpotsWithTaxAdminDataInJsonFile
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// uruchamianie procesu przetwarzania podatków
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// wyświetlanie błędu
print $ex->getMessage() . "\n";
}
// koniec
print "Terminé\n";
exit();
Komentarze
- wiersz 4: wymusza się ścisłe przestrzeganie typów parametrów funkcji;
- wiersz 7: skrypt [main.php] jest również umieszczony w przestrzeni nazw [Application];
- wiersze 10–15: informujemy interpreter PHP, gdzie znajdują się klasy i interfejsy wykorzystywane przez skrypt. Należy zauważyć, że w tym miejscu nie użyliśmy instrukcji use do zadeklarowania pełnych nazw klas wykorzystywanych przez skrypt. Jest to bowiem zbędne, ponieważ skrypt i klasy znajdują się w tej samej przestrzeni nazw [Application];
- wiersze 18–22: nazwy plików tekstowych wykorzystywanych w skrypcie;
- wiersze 24–29: tworzony jest obiekt [ImpotsWithTaxAdminDataInJsonFile] i obsługiwane są ewentualne wyjątki;
- wiersz 28: wywoływana jest metoda [executeBatchImpots], która obliczy podatki dla wszystkich podatników z pliku [TAXPAYERSDATA_FILENAME]. Wyniki zostaną zapisane w pliku [RESULTS_FILENAME], a ewentualne błędy w pliku [ERRORS_FILENAME];
- wiersze 29–32: w przypadku błędu nieusuwalnego wyświetlany jest komunikat o błędzie;
Wyniki
Wraz z plikiem podatników [taxpayersdata.txt] następuje:
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
otrzymujemy następujący plik błędów [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
oraz plik wyników o nazwie [resultats.txt]: