8. Anwendungsübung – Version 3
Wir greifen die zuvor behandelte Übung (Abschnitte 4.3 und 4.4) wieder auf, um sie mit PHP-Code zu lösen, der eine Klasse verwendet.
8.1. Die Verzeichnisstruktur des Skripts

8.2. Die Ausnahme [ExceptionImpots]
In Version 03 löst ein Konstruktor oder eine Klassenmethode, wenn ein Fehler auftritt, die folgende [ExceptionImpots]-Ausnahme aus:
Kommentare
- Zeile 4: Die Klasse [ExceptionImpots] befindet sich im Namespace [Application];
- Zeile 6: Die Klasse [ExceptionImpots] erweitert die vordefinierte PHP-Klasse [RuntimeException];
- Zeile 8: Der Konstruktor erwartet zwei Parameter:
- $message: ist die mit der Ausnahme verbundene Fehlermeldung;
- $code: ist der mit der Ausnahme verbundene Fehlercode. Ist er nicht vorhanden, wird der Code 0 verwendet;
8.3. Die Klasse [TaxAdminData]
In Version 02 wurden Steuerverwaltungsdaten erfasst:
- zunächst in einer JSON-Datei;
- dann aus dieser JSON-Datei in ein assoziatives Array;
In Version 03 befinden sich die Steuerverwaltungsdaten weiterhin in der Datei [taxadmindata.json], jedoch mit anderen Attributnamen:
{
"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
}
In Version 02 wurde diese Datei zur Initialisierung eines assoziativen Arrays verwendet. In Version 03 initialisiert die Datei die folgende Klasse [TaxAdminData]:
<?php
namespace Application;
class TaxAdminData {
// tax brackets
private $limites;
private $coeffR;
private $coeffN;
// tax calculation constants
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// initialization
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// mistake?
if (!$fileContents) {
// we note the error
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// retrieve the jSON code from the configuration file in an associative array
$arrayTaxAdminData = \json_decode($fileContents, true);
// mistake?
if ($arrayTaxAdminData === FALSE) {
// we note the error
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// mistake?
if ($erreur) {
// throw an exception
throw new ExceptionImpots($message);
}
// initialization of class attributes
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// check that all keys have been initialized
$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é");
}
}
// we check that we only have real values
foreach ($this as $key => $value) {
// $value must be a real number >=0 or an array of reals >=0
$result = $this->check($value);
// mistake?
if ($result->erreur) {
// throw an exception
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// we note the value
$this->$key = $result->value;
}
}
// we return the object
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// object's Json string
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getters and setters
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;
}
…
}
Kommentare
- Zeilen 6–20: Die Eigenschaften, die die gleichnamigen Eigenschaften aus der JSON-Datei [taxadmindata.json] enthalten werden. Dies ist ein wichtiger Punkt: Die Eigenschaften der Klasse [TaxAdminData] sind identisch mit denen in der JSON-Datei [taxadmindata.json]. Diese Eigenschaft erleichtert das Schreiben des Codes erheblich;
- Die Klasse [TaxAdminData] hat keinen Konstruktor. In PHP ist es nicht möglich, mehrere Konstruktoren zu haben. Die Definition eines einzigen Konstruktors verhindert daher, dass das Objekt auf andere Weise initialisiert wird. Im weiteren Verlauf werden unsere Klassen keinen Konstruktor haben, sondern stattdessen mehrere Methoden vom Typ [setFromSomething], die es ermöglichen, sie auf verschiedene Arten zu initialisieren. Ein Objekt vom Typ [TaxAdminData] wird dann mit dem folgenden Ausdruck erstellt:
- Zeile 23: Die Methode [setFromJsonFile] initialisiert die Klassenattribute mit den gleichnamigen Attributen in der Datei [$jsonFilename];
- Zeilen 24–42: Die JSON-Datei wird geparst, um das assoziative Array [$arrayTaxAdminData] zu erstellen. Wir haben diesen Code bereits im Skript [main.php] aus Version 02 gesehen;
- Zeilen 44–47: Tritt bei der Verarbeitung der JSON-Datei ein Fehler auf, wird eine Ausnahme ausgelöst. Diese Ausnahme wird bis zum Hauptskript [main.php] weitergeleitet;
- Zeilen 48–51: Die Klassenattribute werden initialisiert. Hier nutzen wir die Tatsache, dass das assoziative Array [$arrayTaxAdminData] und die Klasse [TaxAdminData] Attribute mit denselben Namen wie die Werte aus der JSON-Datei haben;
- Zeilen 53–57: Wir überprüfen, ob alle Attribute der Klasse [TaxAdminData] initialisiert wurden;
- Zeile 53: Der Ausdruck [get_object_vars($this)] gibt ein assoziatives Array zurück, dessen Attribute denen des Objekts [$this] entsprechen, d. h. den Attributen der Klasse [TaxAdminData]. Hier ist es wichtig zu verstehen, dass der Initialisierungsprozess in den Zeilen 48–51 möglicherweise Attribute zum Objekt [$this] hinzugefügt hat. Wenn wir also schreiben:
dann wird das Attribut [x] dem Objekt [$this] hinzugefügt, auch wenn dieses Attribut in der Klasse [TaxAdminData] nicht deklariert wurde. Sicher ist, dass die Attribute in den Zeilen 6–20 tatsächlich Teil des Objekts [$this] sind, aber möglicherweise nicht initialisiert wurden. Dies ist ein leicht zu begehender Fehler; es reicht schon ein Tippfehler im Attributnamen in der Datei [taxadmindata.json];
- Zeilen 54–57: Wir durchlaufen alle Attribute von [$this], und wenn eines davon nicht initialisiert wurde, lösen wir eine Ausnahme aus;
- Ein Attribut kann mit einem falschen Wert initialisiert worden sein. In PHP ist es nicht möglich, einen Typ für Attribute anzugeben. Daher ist die Operation:
ist möglich, obwohl das Attribut [$plafondQfDemiPart] eine reelle Zahl sein sollte;
- Zeilen 59–71: Wir überprüfen, ob jedes Attribut der Klasse einen positiven reellen Wert oder den Wert Null hat. Die Funktion [check] in Zeile 76 führt diese Aufgabe aus. Ihr Parameter [$value] ist entweder ein einzelner Wert oder ein Array von Werten;
- Zeile 62: Die Funktion [check] gibt ein Objekt vom Typ [\stdClass] mit zwei Attributen zurück:
- [error]: TRUE, wenn ein Fehler aufgetreten ist, andernfalls FALSE;
- [value]: der tatsächliche numerische Wert, der dem in Zeile 62 übergebenen Parameter [$value] entspricht;
- Zeile 64: Wir prüfen, ob die Überprüfung erfolgreich war oder nicht;
- Zeile 66: Wenn ein Attribut keine positive reelle Zahl oder Null ist, wird eine Ausnahme ausgelöst;
- Zeile 69: Andernfalls speichern wir seinen numerischen Wert;
- Zeile 73: Das Objekt [$this] wird als Ergebnis zurückgegeben;
Die Funktion [check] sieht wie folgt aus:
private function check($value): \stdClass {
// $value est soit un tableau d'éléments soit un unique élément
// on crée un tableau
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// on transforme le tableau d'éléments de type non connu en tableau de réels
$newTableau = [];
$result = new \stdClass();
// les éléments du tableau doivent être des nombres décimaux positifs ou nuls
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// on met le float dans newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// on note l'erreur
$result->erreur = TRUE;
// on quitte
return $result;
}
}
// on rend le résultat
$result->erreur = FALSE;
if (!\is_array($value)) {
// une seule valeur
$result->value = $newTableau[0];
} else {
// une liste de valeurs
$result->value = $newTableau;
}
return $result;
}
Kommentare
- Zeile 1: Der Parameter [$value] ist entweder ein Array oder ein einzelnes Element. Außerdem ist sein Typ unbekannt. Der Wert stammt aus der Datei [taxadmindata.json]. Je nach den in dieser Datei eingegebenen Werten können die ausgelesenen Werte Ganzzahlen, reelle Zahlen, Zeichenfolgen oder Boolesche Werte sein. Zum Beispiel:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
In Fall 1 ist der Wert vom Typ [Ganzzahl], in Fall 2 vom Typ [Gleitkomma], in Fall 3 vom Typ [Zeichenkette], die in eine Zahl konvertiert werden kann, und in Fall 4 vom Typ [Zeichenkette], die nicht in eine Zahl konvertiert werden kann;
- Zeilen 4–8: Wir erstellen ein Array aus dem in Zeile 1 empfangenen Parameter [$value];
- Zeile 10: das Array, das wir mit reellen Zahlen füllen werden;
- Zeile 11: Das Ergebnis wird ein Objekt vom Typ [\stdClass] sein;
- Zeile 13: Relationaler Ausdruck für eine positive oder null Realzahl;
- Zeilen 14–24: Wir überprüfen, ob alle Elemente des Arrays [$tableau] positive oder null Realzahlen sind, und füllen das Array [$newTableau] mit diesen Elementen, die in den Typ [float] konvertiert wurden (Zeile 17);
- Zeilen 18–23: Sobald ein Element erkannt wird, das keine positive oder null Realzahl ist, wird der Fehler im Ergebnis vermerkt und das Ergebnis zurückgegeben;
- Zeilen 25–34: Fall, in dem alle Elemente des Arrays [$tableau] als gültig deklariert wurden;
- Zeile 32: Der zurückgegebene Wert [$result→value] ist ein Array von Floats oder ein einzelner Float;
Die Funktion [__toString] in den Zeilen 82–85 gibt die JSON-Zeichenkette der Attribute und Werte des Objekts [$this] zurück.
Zeilen 87–110: die Getter und Setter der Klasse;
Hinweis: Es kann manchmal etwas mühsam sein, alle Getter- und Setter-Methoden für eine Klasse schreiben zu müssen, insbesondere wenn es viele Eigenschaften gibt. NetBeans kann diese sowie den Konstruktor automatisch generieren. Wählen Sie dazu einfach die Eigenschaften aus [1]:

- in [2], klicken Sie mit der rechten Maustaste an die Stelle, an der Sie den Code einfügen möchten, und wählen Sie dann die Option [Code einfügen];

- Geben Sie in [4] an, dass Sie den Konstruktor generieren möchten;
- Markieren Sie in [5] alle Attribute: Das bedeutet, dass der Konstruktor für jedes Attribut einen Parameter haben soll;
- Wählen Sie in [6] den Java-Konstruktorstil aus;
- Geben Sie in [7] an, dass Sie das Schlüsselwort [public] explizit vor dem Konstruktor wünschen.
- Klicken Sie in [8] auf „OK“;

- In [9] hat NetBeans den Konstruktor generiert. Es konnte jedoch die Parametertypen nicht angeben, da es diese nicht kennt. Fügen Sie diese selbst hinzu [10];
Um die Getter und Setter zu generieren, wiederholen Sie die Schritte 2–4 und wählen Sie in Schritt 4 [Getter und Setter] aus:

- Geben Sie in [5] an, dass Sie für jedes der Attribute Getter und Setter wünschen;
- Geben Sie in [6] an, dass Sie die Getter und Setter im von Java verwendeten Stil wünschen: setAttribute, getAttribute;
- Geben Sie in [7] an, dass diese Getter und Setter öffentlich sein sollen;
- Klicken Sie in [8] auf „OK“;

- In [9] die von NetBeans generierten Getter und Setter;
Löschen Sie diese Getter und Setter und wiederholen Sie die Schritte 2–7.
- Aktivieren Sie in [8] die Option [Fluent Setter], die wir zuvor nicht aktiviert hatten;
Das Ergebnis sieht wie folgt aus:

Jeder Setter endet mit einer [return $this]-Anweisung. Dadurch können die Attribute wie folgt initialisiert werden:
Tatsächlich ist der Wert von [$data→setLimites($limites)] (Zeile 32 des Codes) [$this], also hier [$data]. Wir können daher die Methode [setCoeffR($coeffR)] dieses Objekts aufrufen und so weiter, da diese Methode wiederum ebenfalls [$this] zurückgibt (Zeile 37 des Codes). Diese Art, Klassenmethoden zu schreiben, bei der Methoden, die nichts zurückgeben sollen, stattdessen das Objekt [$this] zurückgeben, wird als „Fluent Writing“ bezeichnet. Dadurch lassen sich diese Methoden einfacher verwenden.
8.4. Die Schnittstelle [InterfaceImpots]
Wir definieren nun die folgende [InterfaceImpots]-Schnittstelle [InterfaceImpots.php]:
<?php
// namespace
namespace Application;
interface InterfaceImpots {
// retrieve tax bracket data for tax calculations
// can throw the ExceptionImpots exception
public function getTaxAdminData(): TaxAdminData;
// the interface knows how to calculate a tax
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// the interface can process data in text files
// $usersFilename: user data file in the form of marital status, number of children, annual salary
// $resultsFilename: results file with marital status, number of children, annual salary, tax amount
// $errorsFilename: file of errors encountered
// can throw the ExceptionImpots exception
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Kommentare
- Zeile 4: Die Schnittstelle wird im Namespace [Application] platziert;
- Zeile 6: die Schnittstelle zur Berechnung von Steuern;
- Zeile 10: Die Methode [getTaxAdminData] ruft Daten aus der Steuerverwaltung in ein Objekt vom Typ [TaxAdminData] ab, das wir gerade eingeführt haben. Da sich diese Daten in einer Datei, einer Datenbank oder sogar im Netzwerk befinden können, kann es vorkommen, dass die Methode [getTaxAdminData] die Daten nicht abrufen kann. In diesem Fall löst sie eine [ExceptionImpots]-Ausnahme aus. Dies ist die Standardmethode in der objektorientierten Programmierung, um einen Fehler zu signalisieren, der in einer Methode oder einem Konstruktor aufgetreten ist;
- Zeile 13: Die Methode [calculateTax] berechnet die Steuer eines Benutzers;
- Zeile 20: Die Methode [executeBatchImpots] berechnet die Steuer für mehrere Steuerzahler:
- [$usersFileName] ist der Name der Textdatei, die die Daten der Steuerzahler enthält;
- [$resultsFileName] ist der Name der Textdatei, die die Steuerbeträge für diese Steuerzahler enthält;
- [$errorsFileName] ist der Name der Textdatei, die die bei der Verarbeitung dieser Dateien aufgetretenen Fehler enthält;
Der Inhalt der Textdatei [$usersFileName] könnte wie folgt aussehen:
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
Beachten Sie, dass die Zeilen 5 und 7 falsche Einträge enthalten.
Der Inhalt der Textdatei [$resultsFileName] sieht dann wie folgt aus:
und der in der Textdatei [$errorsFileName] lautet wie folgt:
8.5. Die Klasse [Utilities]
Wir definieren außerdem eine [Utilities]-Klasse in einer Datei namens [Utilities.php]:
<?php
// namespace
namespace Application;
// a class of utility functions
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// delete the end-of-line mark from $ligne if it exists
$longueur = strlen($ligne); // line length
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// end - return the line
return($ligne);
}
}
Kommentare
- Zeile 4: Die Klasse [Utilities] wird ebenfalls im Namespace [Examples] platziert;
- Zeile 9: Die Methode [cutNewLineChar] entfernt alle Zeilenendezeichen aus dem Text, der ihr als Parameter übergeben wird. Sie gibt die so gebildete neue Zeile zurück. Beachten Sie, dass es sich um eine statische Methode handelt, was bedeutet, dass sie in der Form [Utilities::cutNewLineChar] aufgerufen wird;
8.6. Die abstrakte Klasse [AbstractBaseImpots]
Die Schnittstelle [InterfaceImpots] wird durch die folgende abstrakte Klasse [AbstractBaseImpots] [AbstractBaseImpots.php] implementiert:
<?php
// namespace
namespace Application;
// definition of an abstract class AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// tax administration data
private $taxAdminData = NULL;
// data required for tax calculation
abstract function getTaxAdminData(): TaxAdminData;
// tAX CALCULATION
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié : yes, no
// $enfants : number of children
// $salaire: annual salary
// $this->taxAdminData: tax administration data
//
// we check that we have the correct data from the tax authorities
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// tax calculation with children
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// tax calculation without children
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// application of the family allowance ceiling
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// we take the highest tax
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"];
}
// calculation of any discount
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// calculation of any tax reduction
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// result
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 {
…
// result
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
private function getRevenuImposable(float $salaire): float {
…
// result
return floor($revenuImposable);
}
// calculates any discount
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// result
return ceil($décôte);
}
// calculates any reduction
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// result
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
Kommentare
- Zeile 4: Die Klasse [AbstractBaseImpots] befindet sich im Namespace [Application], genau wie die anderen Elemente der Anwendung, die gerade geschrieben wird;
- Zeile 7: Die Klasse [AbstractBaseImpots] implementiert die Schnittstelle [InterfaceImpots];
- Zeile 9: Die Steuerverwaltungsdaten werden im Attribut [$taxAdminData] abgelegt;
- Zeile 12: Implementierung der Methode [getTaxAdminData] der Schnittstelle. Wir wissen noch nicht, wie diese Methode definiert werden soll: Im vorigen Abschnitt haben wir ein Beispiel gesehen, bei dem die Steuerverwaltungsdaten aus einer JSON-Datei entnommen wurden. Wir werden einen weiteren Fall betrachten, in dem die Daten aus einer Datenbank abgerufen werden. Es ist Aufgabe der abgeleiteten Klassen, den Inhalt der Methode [getTaxAdminData] zu definieren. Die beiden vorangegangenen Fälle führen zu zwei abgeleiteten Klassen. Die Methode [getTaxAdminData] wird daher als abstrakt deklariert, was die Klasse selbst automatisch abstrakt macht (Zeile 7);
- Zeilen 15–64: die Steuerberechnungsfunktion, die bereits in den Abschnitten link und link behandelt wurde;
- Version 02 speicherte Steuerverwaltungsdaten in einem assoziativen Array [$taxAdminData]. Version 03 speichert sie im Attribut [$this→taxAdminData]. Der erste Unterschied zwischen diesen beiden Ansätzen ist die Sichtbarkeit der Steuerdaten:
- In Version 02 war das assoziative Array [$taxAdminData] nicht global sichtbar. Es wurde daher als Parameter an alle Funktionen zur Steuerberechnung übergeben;
- In Version 03 ist das Attribut [$this→taxAdminData] für alle Methoden der Klasse global sichtbar. Es wird daher nicht mehr als Parameter an alle Steuerberechnungsfunktionen übergeben;
- Ein zweiter Unterschied ergibt sich daraus, dass in Version 03 Funktionen durch Klassenmethoden ersetzt wurden. Jeder Methodenaufruf erfolgt nun über den Ausdruck [$this→getMethod(…)] (Zeilen 27, 31, 57, 60);
- Ein dritter Unterschied besteht darin, dass die Methode [calculateTax] zu Beginn ihrer Ausführung nicht weiß, ob das benötigte Attribut [private $taxAdminData] initialisiert wurde. Dies liegt daran, dass der Klassenkonstruktor es nicht initialisiert. Es ist daher Aufgabe der Methode [calculateTax], dies mithilfe der Methode [getTaxAdminData] in Zeile 12 zu tun. Dies geschieht in den Zeilen 23–25;
- Abgesehen von diesen Unterschieden bleiben die Methoden zur Steuerberechnung dieselben wie in früheren Versionen;
Die Funktion [executeBatchImpots] sieht wie folgt aus:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// pas mal d'erreurs peuvent se produire dès qu'on gère des fichiers
try {
// ouverture fichier des erreurs
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// ouverture fichier des résultats
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// lecture des données utilisateur
// chaque ligne a la forme statut marital, nombre d'enfants, salaire annuel
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// on exploite la ligne courante du fichier des données utilisateur
// qui a la forme statut marital, nombre d'enfants, salaire annuel
$num = 1; // n° ligne courante
$nbErreurs = 0; // nbre d'erreurs rencontrées
while ($ligne = fgets($data, 100)) {
// debug
// print "ligne n° " . ($i + 1) . " : " . $ligne;
// on enlève l'éventuelle marque de fin de ligne
$ligne = Utilitaires::cutNewLineChar($ligne);
// on récupère les 3 champs marié:enfants:salaire qui forment $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// on les vérifie
// le statut marital doit être oui ou non
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// le nombre d'enfants doit être un entier
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// le salaire est un entier sans les centimes d'euros
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// erreur ?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// on calcule l'impôt
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// on inscrit le résultat dans le fichier des résultats
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// ligne suivante
$num++;
}
// des erreurs ?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// on relance l'exception
throw $ex;
} finally {
// on ferme tous les fichiers
fclose($data);
fclose($results);
fclose($errors);
}
}
Code-Kommentare
- Zeile 1: Die Funktion nimmt drei Parameter entgegen:
- [$usersFileName]: Der Name der Textdatei, die die Steuerzahlerdaten enthält. Jede Textzeile enthält die Daten eines Steuerzahlers im folgenden Format: Familienstand (ja/nein), Anzahl der Kinder, Jahresgehalt:
- (Fortsetzung)
- [$resultsFileName]: Der Name der Textdatei, die die Ergebnisse enthält. Jede Textzeile hat das folgende Format:
- (Fortsetzung)
- [$errorsFileName]: Der Name der Textdatei, die Fehler enthält:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- Zeile 3: Da bei einer Reihe von Operationen eine Ausnahme ausgelöst werden kann, umgibt ein try/catch/finally-Block den gesamten Methodencode;
- Zeilen 3–19: Die drei Dateien werden geöffnet. Sobald ein Öffnungsvorgang fehlschlägt, wird eine Ausnahme ausgelöst;
- Zeile 24: Die Zeilen der Datei [$data] werden nacheinander in Blöcken von bis zu 100 Zeichen gelesen (die Zeilen sind alle kürzer als 100 Zeichen);
- Zeile 28: Die statische Methode [Utilities::cutNewLineChar] wird verwendet, um alle Zeilenendezeichen zu entfernen;
- Zeile 30: Die drei Elemente der gelesenen Zeile werden abgerufen;
- Zeilen 33–52: Die Gültigkeit der drei Elemente wird überprüft. Hier wird bei einem Fehler keine Ausnahme ausgelöst, sondern die Fehlermeldung in die Textdatei [$errors] geschrieben (Zeile 55);
- Zeile 59: Ist die gelesene Zeile gültig, wird die Steuer berechnet. Das Ergebnis wird als assoziatives Array ["tax" => floor($tax), "surcharge" => $surcharge, "discount" => $discount, "reduction" => $reduction, "rate" => $rate] zurückgegeben;
- Zeile 61: Die Schlüssel [married, children, salary] werden dem Ergebnis hinzugefügt;
- Zeile 61: Das Ergebnis wird in Form der JSON-Zeichenkette des erhaltenen Ergebnisses in die Textdatei [$results] geschrieben;
- Zeilen 68–70: Am Ende der Verarbeitung der Datei [$data] überprüfen wir die Anzahl der aufgetretenen fehlerhaften Zeilen. Wenn mindestens eine vorhanden ist, lösen wir eine Ausnahme aus;
- Zeilen 71–74: Wir fangen die Ausnahme ab, die der Code möglicherweise ausgelöst hat, und lösen sie sofort erneut aus (Zeile 73). Der Zweck dieser Technik besteht darin, einen [finally]-Block in den Zeilen 74–79 einzufügen: Unabhängig davon, wie die Codeausführung der Methode endet, werden die drei Dateien geschlossen, die möglicherweise durch diesen Code geöffnet wurden. Das Schließen einer Datei, die nicht geöffnet wurde, verursacht keinen Fehler;
8.7. Die Klasse [ImpotsWithTaxAdminDataInJsonFile]
Die abstrakte Klasse [AbstractBaseImpots] implementiert die Methode [getTaxAdminData] der Schnittstelle [InterfaceImpots] nicht. Wir müssen sie daher in einer abgeleiteten Klasse definieren. Dies tun wir in der folgenden abgeleiteten Klasse [ImpotsWithTaxAdminDataInJsonFile]:
<?php
// namespace
namespace Application;
// definition of a ImpotsWithDataInArrays class
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// a Data attribute
private $taxAdminData;
// the manufacturer
public function __construct(string $jsonFileName) {
// initialize $this->taxAdminData from file jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// returns data for tax calculation
public function getTaxAdminData(): TaxAdminData {
// we make the attribute [$this->taxAdminData]
return $this->taxAdminData;
}
}
Kommentare
- Zeile 7: Die Klasse [ImpotsWithTaxAdminDataInJsonFile] erweitert die abstrakte Klasse [AbstractBaseImpots]. Sie muss die Methode [getTaxAdminData] definieren, die ihre übergeordnete Klasse nicht definiert hat;
- Zeile 9: Das Attribut [$taxAdminData] enthält die Steuerverwaltungsdaten;
- Zeilen 12–15: Der Konstruktor erhält als einzigen Parameter den Namen der JSON-Datei, die die Steuerdaten enthält;
- Zeile 14: Ein Objekt vom Typ [TaxAdminData] wird erstellt und anschließend initialisiert. Dieser Vorgang kann eine Ausnahme vom Typ [ExceptionImpots] auslösen. Diese Ausnahme wird bis zum Hauptskript [main.php] weitergeleitet;
- Zeilen 18–20: Wir definieren einen Körper für die Methode [getTaxAdminData], die die übergeordnete Klasse nicht definiert hatte. Hier geben wir einfach das vom Konstruktor initialisierte Attribut [$this->taxAdminData] zurück;
8.8. Das Skript [main.php]
Diese Klassen und Schnittstellen werden vom folgenden Skript [main.php] verwendet:
<?php
// strict adherence to declared types of function parameters
declare(strict_types = 1);
// namespace
namespace Application;
// interface and class inclusion
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 -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// create a ImpotsWithTaxAdminDataInJsonFile object
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// we execute the tax batch
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit();
Kommentare
- Zeile 4: Die strikte Einhaltung der Funktionsparametertypen wird erzwungen;
- Zeile 7: Das Skript [main.php] wird ebenfalls im Namespace [Application] platziert;
- Zeilen 10–15: Wir teilen dem PHP-Interpreter mit, wo sich die vom Skript verwendeten Klassen und Schnittstellen befinden. Beachten Sie, dass wir hier keine `use`-Anweisung verwendet haben, um die vollständigen Namen der vom Skript verwendeten Klassen zu deklarieren. Dies ist nicht erforderlich, da sich das Skript und die Klassen im selben Namespace [Application] befinden;
- Zeilen 18–22: Die Namen der im Skript verwendeten Textdateien;
- Zeilen 24–29: Ein [ImpotsWithTaxAdminDataInJsonFile]-Objekt wird erstellt, und eventuelle Ausnahmen werden behandelt;
- Zeile 28: Die Methode [executeBatchImpots] wird ausgeführt, die die Steuern für alle Steuerzahler in der Datei [TAXPAYERSDATA_FILENAME] berechnet. Die Ergebnisse werden in der Datei [RESULTS_FILENAME] gespeichert, etwaige Fehler in der Datei [ERRORS_FILENAME];
- Zeilen 29–32: Im Falle eines schwerwiegenden Fehlers wird die Fehlermeldung angezeigt;
Ergebnisse
Mit der folgenden Steuerzahlerdatei [taxpayersdata.txt]:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000
Wir erhalten die folgende Fehlerdatei [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
und die folgende Ergebnisdatei [resultats.txt]: